Merge branch 'master' into issue-467
This commit is contained in:
commit
8b31b283f5
11 changed files with 465 additions and 82 deletions
|
|
@ -185,20 +185,23 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str
|
|||
}
|
||||
|
||||
query := fragments + `
|
||||
query($owner: String!, $repo: String!, $limit: Int, $states: [IssueState!] = OPEN, $labels: [String!], $assignee: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
hasIssuesEnabled
|
||||
issues(first: $limit, orderBy: {field: CREATED_AT, direction: DESC}, states: $states, labels: $labels, filterBy: {assignee: $assignee}) {
|
||||
nodes {
|
||||
...issue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
query($owner: String!, $repo: String!, $limit: Int, $endCursor: String, $states: [IssueState!] = OPEN, $labels: [String!], $assignee: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
hasIssuesEnabled
|
||||
issues(first: $limit, after: $endCursor, orderBy: {field: CREATED_AT, direction: DESC}, states: $states, labels: $labels, filterBy: {assignee: $assignee}) {
|
||||
nodes {
|
||||
...issue
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"limit": limit,
|
||||
"owner": repo.RepoOwner(),
|
||||
"repo": repo.RepoName(),
|
||||
"states": states,
|
||||
|
|
@ -210,25 +213,49 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str
|
|||
variables["assignee"] = assigneeString
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
var response struct {
|
||||
Repository struct {
|
||||
Issues struct {
|
||||
Nodes []Issue
|
||||
Nodes []Issue
|
||||
PageInfo struct {
|
||||
HasNextPage bool
|
||||
EndCursor string
|
||||
}
|
||||
}
|
||||
HasIssuesEnabled bool
|
||||
}
|
||||
}
|
||||
|
||||
err := client.GraphQL(query, variables, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var issues []Issue
|
||||
pageLimit := min(limit, 100)
|
||||
|
||||
loop:
|
||||
for {
|
||||
variables["limit"] = pageLimit
|
||||
err := client.GraphQL(query, variables, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Repository.HasIssuesEnabled {
|
||||
return nil, fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(repo))
|
||||
}
|
||||
|
||||
for _, issue := range response.Repository.Issues.Nodes {
|
||||
issues = append(issues, issue)
|
||||
if len(issues) == limit {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if response.Repository.Issues.PageInfo.HasNextPage {
|
||||
variables["endCursor"] = response.Repository.Issues.PageInfo.EndCursor
|
||||
pageLimit = min(pageLimit, limit-len(issues))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !resp.Repository.HasIssuesEnabled {
|
||||
return nil, fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(repo))
|
||||
}
|
||||
|
||||
return resp.Repository.Issues.Nodes, nil
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func IssueByNumber(client *Client, repo ghrepo.Interface, number int) (*Issue, error) {
|
||||
|
|
|
|||
68
api/queries_issue_test.go
Normal file
68
api/queries_issue_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
)
|
||||
|
||||
func TestIssueList(t *testing.T) {
|
||||
http := &FakeHTTP{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"hasIssuesEnabled": true,
|
||||
"issues": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": true,
|
||||
"endCursor": "ENDCURSOR"
|
||||
}
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"hasIssuesEnabled": true,
|
||||
"issues": {
|
||||
"nodes": [],
|
||||
"pageInfo": {
|
||||
"hasNextPage": false,
|
||||
"endCursor": "ENDCURSOR"
|
||||
}
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
|
||||
_, err := IssueList(client, ghrepo.FromFullName("OWNER/REPO"), "open", []string{}, "", 251)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(http.Requests) != 2 {
|
||||
t.Fatalf("expected 2 HTTP requests, seen %d", len(http.Requests))
|
||||
}
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[0].Body)
|
||||
json.Unmarshal(bodyBytes, &reqBody)
|
||||
if reqLimit := reqBody.Variables["limit"].(float64); reqLimit != 100 {
|
||||
t.Errorf("expected 100, got %v", reqLimit)
|
||||
}
|
||||
if _, cursorPresent := reqBody.Variables["endCursor"]; cursorPresent {
|
||||
t.Error("did not expect first request to pass 'endCursor'")
|
||||
}
|
||||
|
||||
bodyBytes, _ = ioutil.ReadAll(http.Requests[1].Body)
|
||||
json.Unmarshal(bodyBytes, &reqBody)
|
||||
if endCursor := reqBody.Variables["endCursor"].(string); endCursor != "ENDCURSOR" {
|
||||
t.Errorf("expected %q, got %q", "ENDCURSOR", endCursor)
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ type PullRequest struct {
|
|||
}
|
||||
}
|
||||
IsCrossRepository bool
|
||||
IsDraft bool
|
||||
MaintainerCanModify bool
|
||||
|
||||
ReviewDecision string
|
||||
|
|
@ -157,6 +158,7 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
|
|||
login
|
||||
}
|
||||
isCrossRepository
|
||||
isDraft
|
||||
commits(last: 1) {
|
||||
nodes {
|
||||
commit {
|
||||
|
|
@ -367,6 +369,7 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, branch string)
|
|||
login
|
||||
}
|
||||
isCrossRepository
|
||||
isDraft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -461,6 +464,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
|
|||
login
|
||||
}
|
||||
isCrossRepository
|
||||
isDraft
|
||||
}
|
||||
`
|
||||
|
||||
|
|
@ -498,7 +502,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
|
|||
}
|
||||
}`
|
||||
|
||||
prs := []PullRequest{}
|
||||
var prs []PullRequest
|
||||
pageLimit := min(limit, 100)
|
||||
variables := map[string]interface{}{}
|
||||
|
||||
|
|
@ -556,7 +560,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
|
|||
variables[name] = val
|
||||
}
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
variables["limit"] = pageLimit
|
||||
var data response
|
||||
|
|
@ -572,17 +576,16 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
|
|||
for _, edge := range prData.Edges {
|
||||
prs = append(prs, edge.Node)
|
||||
if len(prs) == limit {
|
||||
goto done
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if prData.PageInfo.HasNextPage {
|
||||
variables["endCursor"] = prData.PageInfo.EndCursor
|
||||
pageLimit = min(pageLimit, limit-len(prs))
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
done:
|
||||
break
|
||||
}
|
||||
|
||||
return prs, nil
|
||||
|
|
|
|||
|
|
@ -109,9 +109,9 @@ func issueList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(colorableErr(cmd), "\nIssues for %s\n\n", ghrepo.FullName(*baseRepo))
|
||||
fmt.Fprintf(colorableErr(cmd), "\nIssues for %s\n\n", ghrepo.FullName(baseRepo))
|
||||
|
||||
issues, err := api.IssueList(apiClient, *baseRepo, state, labels, assignee, limit)
|
||||
issues, err := api.IssueList(apiClient, baseRepo, state, labels, assignee, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ func issueStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
issuePayload, err := api.IssueStatus(apiClient, *baseRepo, currentUser)
|
||||
issuePayload, err := api.IssueStatus(apiClient, baseRepo, currentUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ func issueStatus(cmd *cobra.Command, args []string) error {
|
|||
out := colorableOut(cmd)
|
||||
|
||||
fmt.Fprintln(out, "")
|
||||
fmt.Fprintf(out, "Relevant issues in %s\n", ghrepo.FullName(*baseRepo))
|
||||
fmt.Fprintf(out, "Relevant issues in %s\n", ghrepo.FullName(baseRepo))
|
||||
fmt.Fprintln(out, "")
|
||||
|
||||
printHeader(out, "Issues assigned to you")
|
||||
|
|
@ -221,7 +221,7 @@ func issueView(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
issue, err := issueFromArg(apiClient, *baseRepo, args[0])
|
||||
issue, err := issueFromArg(apiClient, baseRepo, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -294,8 +294,6 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(colorableErr(cmd), "\nCreating issue in %s\n\n", ghrepo.FullName(*baseRepo))
|
||||
|
||||
baseOverride, err := cmd.Flags().GetString("repo")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -309,31 +307,6 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
||||
// TODO: move URL generation into GitHubRepository
|
||||
openURL := fmt.Sprintf("https://github.com/%s/issues/new", ghrepo.FullName(*baseRepo))
|
||||
if len(templateFiles) > 1 {
|
||||
openURL += "/choose"
|
||||
}
|
||||
cmd.Printf("Opening %s in your browser.\n", openURL)
|
||||
return utils.OpenInBrowser(openURL)
|
||||
}
|
||||
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := api.GitHubRepo(apiClient, *baseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !repo.HasIssuesEnabled {
|
||||
return fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(*baseRepo))
|
||||
}
|
||||
|
||||
action := SubmitAction
|
||||
|
||||
title, err := cmd.Flags().GetString("title")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse title: %w", err)
|
||||
|
|
@ -343,6 +316,39 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("could not parse body: %w", err)
|
||||
}
|
||||
|
||||
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
||||
// TODO: move URL generation into GitHubRepository
|
||||
openURL := fmt.Sprintf("https://github.com/%s/issues/new", ghrepo.FullName(baseRepo))
|
||||
if title != "" || body != "" {
|
||||
openURL += fmt.Sprintf(
|
||||
"?title=%s&body=%s",
|
||||
url.QueryEscape(title),
|
||||
url.QueryEscape(body),
|
||||
)
|
||||
} else if len(templateFiles) > 1 {
|
||||
openURL += "/choose"
|
||||
}
|
||||
cmd.Printf("Opening %s in your browser.\n", displayURL(openURL))
|
||||
return utils.OpenInBrowser(openURL)
|
||||
}
|
||||
|
||||
fmt.Fprintf(colorableErr(cmd), "\nCreating issue in %s\n\n", ghrepo.FullName(baseRepo))
|
||||
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := api.GitHubRepo(apiClient, baseRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !repo.HasIssuesEnabled {
|
||||
return fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(baseRepo))
|
||||
}
|
||||
|
||||
action := SubmitAction
|
||||
|
||||
interactive := title == "" || body == ""
|
||||
|
||||
if interactive {
|
||||
|
|
@ -370,7 +376,7 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
if action == PreviewAction {
|
||||
openURL := fmt.Sprintf(
|
||||
"https://github.com/%s/issues/new/?title=%s&body=%s",
|
||||
ghrepo.FullName(*baseRepo),
|
||||
ghrepo.FullName(baseRepo),
|
||||
url.QueryEscape(title),
|
||||
url.QueryEscape(body),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/utils"
|
||||
|
|
@ -492,5 +493,31 @@ func TestIssueCreate_web(t *testing.T) {
|
|||
}
|
||||
url := seenCmd.Args[len(seenCmd.Args)-1]
|
||||
eq(t, url, "https://github.com/OWNER/REPO/issues/new")
|
||||
eq(t, output.String(), "Opening https://github.com/OWNER/REPO/issues/new in your browser.\n")
|
||||
eq(t, output.String(), "Opening github.com/OWNER/REPO/issues/new in your browser.\n")
|
||||
eq(t, output.Stderr(), "")
|
||||
}
|
||||
|
||||
func TestIssueCreate_webTitleBody(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "master")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
return &outputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand(issueCreateCmd, `issue create -w -t mytitle -b mybody`)
|
||||
if err != nil {
|
||||
t.Errorf("error running command `issue create`: %v", err)
|
||||
}
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
url := strings.ReplaceAll(seenCmd.Args[len(seenCmd.Args)-1], "^", "")
|
||||
eq(t, url, "https://github.com/OWNER/REPO/issues/new?title=mytitle&body=mybody")
|
||||
eq(t, output.String(), "Opening github.com/OWNER/REPO/issues/new in your browser.\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func prStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
prPayload, err := api.PullRequests(apiClient, *baseRepo, currentPRNumber, currentPRHeadRef, currentUser)
|
||||
prPayload, err := api.PullRequests(apiClient, baseRepo, currentPRNumber, currentPRHeadRef, currentUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ func prStatus(cmd *cobra.Command, args []string) error {
|
|||
out := colorableOut(cmd)
|
||||
|
||||
fmt.Fprintln(out, "")
|
||||
fmt.Fprintf(out, "Relevant pull requests in %s\n", ghrepo.FullName(*baseRepo))
|
||||
fmt.Fprintf(out, "Relevant pull requests in %s\n", ghrepo.FullName(baseRepo))
|
||||
fmt.Fprintln(out, "")
|
||||
|
||||
printHeader(out, "Current branch")
|
||||
|
|
@ -136,7 +136,7 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(colorableErr(cmd), "\nPull requests for %s\n\n", ghrepo.FullName(*baseRepo))
|
||||
fmt.Fprintf(colorableErr(cmd), "\nPull requests for %s\n\n", ghrepo.FullName(baseRepo))
|
||||
|
||||
limit, err := cmd.Flags().GetInt("limit")
|
||||
if err != nil {
|
||||
|
|
@ -174,8 +174,8 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"owner": (*baseRepo).RepoOwner(),
|
||||
"repo": (*baseRepo).RepoName(),
|
||||
"owner": baseRepo.RepoOwner(),
|
||||
"repo": baseRepo.RepoName(),
|
||||
"state": graphqlState,
|
||||
}
|
||||
if len(labels) > 0 {
|
||||
|
|
@ -214,7 +214,7 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
if table.IsTTY() {
|
||||
prNum = "#" + prNum
|
||||
}
|
||||
table.AddField(prNum, nil, colorFuncForState(pr.State))
|
||||
table.AddField(prNum, nil, colorFuncForPR(pr))
|
||||
table.AddField(replaceExcessiveWhitespace(pr.Title), nil, nil)
|
||||
table.AddField(pr.HeadLabel(), nil, utils.Cyan)
|
||||
table.EndRow()
|
||||
|
|
@ -227,6 +227,14 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func colorFuncForPR(pr api.PullRequest) func(string) string {
|
||||
if pr.State == "OPEN" && pr.IsDraft {
|
||||
return utils.Gray
|
||||
} else {
|
||||
return colorFuncForState(pr.State)
|
||||
}
|
||||
}
|
||||
|
||||
func colorFuncForState(state string) func(string) string {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
|
|
@ -261,7 +269,7 @@ func prView(cmd *cobra.Command, args []string) error {
|
|||
var openURL string
|
||||
var pr *api.PullRequest
|
||||
if len(args) > 0 {
|
||||
pr, err = prFromArg(apiClient, *baseRepo, args[0])
|
||||
pr, err = prFromArg(apiClient, baseRepo, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -273,15 +281,15 @@ func prView(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if prNumber > 0 {
|
||||
openURL = fmt.Sprintf("https://github.com/%s/pull/%d", ghrepo.FullName(*baseRepo), prNumber)
|
||||
openURL = fmt.Sprintf("https://github.com/%s/pull/%d", ghrepo.FullName(baseRepo), prNumber)
|
||||
if preview {
|
||||
pr, err = api.PullRequestByNumber(apiClient, *baseRepo, prNumber)
|
||||
pr, err = api.PullRequestByNumber(apiClient, baseRepo, prNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pr, err = api.PullRequestForBranch(apiClient, *baseRepo, branchWithOwner)
|
||||
pr, err = api.PullRequestForBranch(apiClient, baseRepo, branchWithOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -385,14 +393,17 @@ func prSelectorForCurrentBranch(ctx context.Context) (prNumber int, prHeadRef st
|
|||
func printPrs(w io.Writer, totalCount int, prs ...api.PullRequest) {
|
||||
for _, pr := range prs {
|
||||
prNumber := fmt.Sprintf("#%d", pr.Number)
|
||||
if pr.State == "OPEN" {
|
||||
fmt.Fprintf(w, " %s", utils.Green(prNumber))
|
||||
|
||||
prNumberColorFunc := utils.Green
|
||||
if pr.IsDraft {
|
||||
prNumberColorFunc = utils.Gray
|
||||
} else if pr.State == "MERGED" {
|
||||
fmt.Fprintf(w, " %s", utils.Magenta(prNumber))
|
||||
prNumberColorFunc = utils.Magenta
|
||||
} else if pr.State == "CLOSED" {
|
||||
fmt.Fprintf(w, " %s", utils.Red(prNumber))
|
||||
prNumberColorFunc = utils.Red
|
||||
}
|
||||
fmt.Fprintf(w, " %s %s", text.Truncate(50, replaceExcessiveWhitespace(pr.Title)), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
fmt.Fprintf(w, " %s %s %s", prNumberColorFunc(prNumber), text.Truncate(50, replaceExcessiveWhitespace(pr.Title)), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
checks := pr.ChecksStatus()
|
||||
reviews := pr.ReviewStatus()
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
if didForkRepo && pushTries < maxPushTries {
|
||||
pushTries++
|
||||
// first wait 2 seconds after forking, then 4s, then 6s
|
||||
time.Sleep(time.Duration(2*pushTries) * time.Second)
|
||||
waitSeconds := 2 * pushTries
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
||||
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
|
|
|
|||
87
command/repo.go
Normal file
87
command/repo.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(repoCmd)
|
||||
repoCmd.AddCommand(repoCloneCmd)
|
||||
repoCmd.AddCommand(repoViewCmd)
|
||||
}
|
||||
|
||||
var repoCmd = &cobra.Command{
|
||||
Use: "repo",
|
||||
Short: "View repositories",
|
||||
Long: `Work with GitHub repositories.
|
||||
|
||||
A repository can be supplied as an argument in any of the following formats:
|
||||
- "OWNER/REPO"
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO"`,
|
||||
}
|
||||
|
||||
var repoCloneCmd = &cobra.Command{
|
||||
Use: "clone <repo>",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Short: "Clone a repository locally",
|
||||
Long: `Clone a GitHub repository locally.
|
||||
|
||||
To pass 'git clone' options, separate them with '--'.`,
|
||||
RunE: repoClone,
|
||||
}
|
||||
|
||||
var repoViewCmd = &cobra.Command{
|
||||
Use: "view [<repo>]",
|
||||
Short: "View a repository in the browser",
|
||||
Long: `View a GitHub repository in the browser.
|
||||
|
||||
With no argument, the repository for the current directory is opened.`,
|
||||
RunE: repoView,
|
||||
}
|
||||
|
||||
func repoClone(cmd *cobra.Command, args []string) error {
|
||||
cloneURL := args[0]
|
||||
if !strings.Contains(cloneURL, ":") {
|
||||
cloneURL = fmt.Sprintf("https://github.com/%s.git", cloneURL)
|
||||
}
|
||||
|
||||
cloneArgs := []string{"clone"}
|
||||
cloneArgs = append(cloneArgs, args[1:]...)
|
||||
cloneArgs = append(cloneArgs, cloneURL)
|
||||
|
||||
cloneCmd := git.GitCommand(cloneArgs...)
|
||||
cloneCmd.Stdin = os.Stdin
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
cloneCmd.Stderr = os.Stderr
|
||||
return utils.PrepareCmd(cloneCmd).Run()
|
||||
}
|
||||
|
||||
func repoView(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
|
||||
var openURL string
|
||||
if len(args) == 0 {
|
||||
baseRepo, err := determineBaseRepo(cmd, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
openURL = fmt.Sprintf("https://github.com/%s", ghrepo.FullName(baseRepo))
|
||||
} else {
|
||||
repoArg := args[0]
|
||||
if strings.HasPrefix(repoArg, "http:/") || strings.HasPrefix(repoArg, "https:/") {
|
||||
openURL = repoArg
|
||||
} else {
|
||||
openURL = fmt.Sprintf("https://github.com/%s", repoArg)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", displayURL(openURL))
|
||||
return utils.OpenInBrowser(openURL)
|
||||
}
|
||||
149
command/repo_test.go
Normal file
149
command/repo_test.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/utils"
|
||||
)
|
||||
|
||||
func TestRepoClone(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "shorthand",
|
||||
args: "repo clone OWNER/REPO",
|
||||
want: "git clone https://github.com/OWNER/REPO.git",
|
||||
},
|
||||
{
|
||||
name: "clone arguments",
|
||||
args: "repo clone OWNER/REPO -- -o upstream --depth 1",
|
||||
want: "git clone -o upstream --depth 1 https://github.com/OWNER/REPO.git",
|
||||
},
|
||||
{
|
||||
name: "HTTPS URL",
|
||||
args: "repo clone https://github.com/OWNER/REPO",
|
||||
want: "git clone https://github.com/OWNER/REPO",
|
||||
},
|
||||
{
|
||||
name: "SSH URL",
|
||||
args: "repo clone git@github.com:OWNER/REPO.git",
|
||||
want: "git clone git@github.com:OWNER/REPO.git",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
return &outputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand(repoViewCmd, tt.args)
|
||||
if err != nil {
|
||||
t.Fatalf("error running command `repo clone`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "")
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoView(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "master")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
return &outputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand(repoViewCmd, "repo view")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo view`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "")
|
||||
eq(t, output.Stderr(), "Opening github.com/OWNER/REPO in your browser.\n")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
url := seenCmd.Args[len(seenCmd.Args)-1]
|
||||
eq(t, url, "https://github.com/OWNER/REPO")
|
||||
}
|
||||
|
||||
func TestRepoView_ownerRepo(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
initFakeHTTP()
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
return &outputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand(repoViewCmd, "repo view cli/cli")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo view`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "")
|
||||
eq(t, output.Stderr(), "Opening github.com/cli/cli in your browser.\n")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
url := seenCmd.Args[len(seenCmd.Args)-1]
|
||||
eq(t, url, "https://github.com/cli/cli")
|
||||
}
|
||||
|
||||
func TestRepoView_fullURL(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
initFakeHTTP()
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
return &outputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand(repoViewCmd, "repo view https://github.com/cli/cli")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo view`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "")
|
||||
eq(t, output.Stderr(), "Opening github.com/cli/cli in your browser.\n")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
url := seenCmd.Args[len(seenCmd.Args)-1]
|
||||
eq(t, url, "https://github.com/cli/cli")
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ func changelogURL(version string) string {
|
|||
return url
|
||||
}
|
||||
|
||||
func determineBaseRepo(cmd *cobra.Command, ctx context.Context) (*ghrepo.Interface, error) {
|
||||
func determineBaseRepo(cmd *cobra.Command, ctx context.Context) (ghrepo.Interface, error) {
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -192,11 +192,10 @@ func determineBaseRepo(cmd *cobra.Command, ctx context.Context) (*ghrepo.Interfa
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var baseRepo ghrepo.Interface
|
||||
baseRepo, err = repoContext.BaseRepo()
|
||||
baseRepo, err := repoContext.BaseRepo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &baseRepo, nil
|
||||
return baseRepo, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -70,8 +71,11 @@ func UncommittedChangeCount() (int, error) {
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// Push publishes a git ref to a remote and sets up upstream configuration
|
||||
func Push(remote string, ref string) error {
|
||||
pushCmd := GitCommand("push", "--set-upstream", remote, ref)
|
||||
pushCmd.Stdout = os.Stdout
|
||||
pushCmd.Stderr = os.Stderr
|
||||
return utils.PrepareCmd(pushCmd).Run()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue