diff --git a/api/queries_pr.go b/api/queries_pr.go index 734245acb..b6fdcb271 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -497,7 +497,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([] } }` - prs := []PullRequest{} + var prs []PullRequest pageLimit := min(limit, 100) variables := map[string]interface{}{} @@ -555,7 +555,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([] variables[name] = val } } - +loop: for { variables["limit"] = pageLimit var data response @@ -571,17 +571,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 diff --git a/command/issue.go b/command/issue.go index 3ba00ad4c..686baaa99 100644 --- a/command/issue.go +++ b/command/issue.go @@ -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), ) diff --git a/command/issue_test.go b/command/issue_test.go index c09de2c62..f8a5d6ad9 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -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") } diff --git a/command/pr.go b/command/pr.go index 9d3e2487e..cd767f13d 100644 --- a/command/pr.go +++ b/command/pr.go @@ -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 { @@ -261,7 +261,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 +273,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 } diff --git a/command/pr_create.go b/command/pr_create.go index 64f43c895..c40f5d8c9 100644 --- a/command/pr_create.go +++ b/command/pr_create.go @@ -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 diff --git a/command/repo.go b/command/repo.go new file mode 100644 index 000000000..b00c3d87b --- /dev/null +++ b/command/repo.go @@ -0,0 +1,57 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/utils" + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(repoCmd) + 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 repoViewCmd = &cobra.Command{ + Use: "view []", + 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 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) +} diff --git a/command/repo_test.go b/command/repo_test.go new file mode 100644 index 000000000..6c4d6f1ef --- /dev/null +++ b/command/repo_test.go @@ -0,0 +1,96 @@ +package command + +import ( + "os/exec" + "testing" + + "github.com/cli/cli/context" + "github.com/cli/cli/utils" +) + +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") +} diff --git a/command/root.go b/command/root.go index 0257b6a48..5130f74c9 100644 --- a/command/root.go +++ b/command/root.go @@ -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 } diff --git a/git/git.go b/git/git.go index 92d2dfd0f..dfd45bade 100644 --- a/git/git.go +++ b/git/git.go @@ -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() }