From 147cdcdbb495345ef5856d2173375fe7ba16ed51 Mon Sep 17 00:00:00 2001 From: ShubhankarKG Date: Fri, 4 Sep 2020 18:24:31 +0530 Subject: [PATCH 01/41] Add extra confirm step and a flag --- pkg/cmd/pr/merge/merge.go | 146 +++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 66 deletions(-) diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 94e0c959c..3ec875157 100644 --- a/pkg/cmd/pr/merge/merge.go +++ b/pkg/cmd/pr/merge/merge.go @@ -33,6 +33,7 @@ type MergeOptions struct { DeleteLocalBranch bool MergeMethod api.PullRequestMergeMethod InteractiveMode bool + ConfirmSubmit bool } func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Command { @@ -107,7 +108,7 @@ func NewCmdMerge(f *cmdutil.Factory, runF func(*MergeOptions) error) *cobra.Comm cmd.Flags().BoolVarP(&flagMerge, "merge", "m", false, "Merge the commits with the base branch") cmd.Flags().BoolVarP(&flagRebase, "rebase", "r", false, "Rebase the commits onto the base branch") cmd.Flags().BoolVarP(&flagSquash, "squash", "s", false, "Squash the commits into one commit and merge it into the base branch") - + cmd.Flags().BoolVarP(&opts.ConfirmSubmit, "confirm", "y", false, "Confirm Merging the PR (default: false)") return cmd } @@ -145,79 +146,92 @@ func mergeRun(opts *MergeOptions) error { } } - var action string - if mergeMethod == api.PullRequestMergeMethodRebase { - action = "Rebased and merged" - err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodRebase) - } else if mergeMethod == api.PullRequestMergeMethodSquash { - action = "Squashed and merged" - err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodSquash) - } else if mergeMethod == api.PullRequestMergeMethodMerge { - action = "Merged" - err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodMerge) - } else { - err = fmt.Errorf("unknown merge method (%d) used", mergeMethod) - return err + shouldSubmitFromFlag := opts.ConfirmSubmit + if !shouldSubmitFromFlag { + fmt.Println(opts.ConfirmSubmit) + err := prompt.Confirm("Submit? ", &shouldSubmitFromFlag) + if err != nil { + return err + } } - if err != nil { - return fmt.Errorf("API call failed: %w", err) - } - - isTerminal := opts.IO.IsStdoutTTY() - - if isTerminal { - fmt.Fprintf(opts.IO.ErrOut, "%s %s pull request #%d (%s)\n", utils.Magenta("✔"), action, pr.Number, pr.Title) - } - - if deleteBranch { - branchSwitchString := "" - - if opts.DeleteLocalBranch && !crossRepoPR { - currentBranch, err := opts.Branch() - if err != nil { - return err - } - - var branchToSwitchTo string - if currentBranch == pr.HeadRefName { - branchToSwitchTo, err = api.RepoDefaultBranch(apiClient, baseRepo) - if err != nil { - return err - } - err = git.CheckoutBranch(branchToSwitchTo) - if err != nil { - return err - } - } - - localBranchExists := git.HasLocalBranch(pr.HeadRefName) - if localBranchExists { - err = git.DeleteLocalBranch(pr.HeadRefName) - if err != nil { - err = fmt.Errorf("failed to delete local branch %s: %w", utils.Cyan(pr.HeadRefName), err) - return err - } - } - - if branchToSwitchTo != "" { - branchSwitchString = fmt.Sprintf(" and switched to branch %s", utils.Cyan(branchToSwitchTo)) - } + if shouldSubmitFromFlag { + var action string + if mergeMethod == api.PullRequestMergeMethodRebase { + action = "Rebased and merged" + err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodRebase) + } else if mergeMethod == api.PullRequestMergeMethodSquash { + action = "Squashed and merged" + err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodSquash) + } else if mergeMethod == api.PullRequestMergeMethodMerge { + action = "Merged" + err = api.PullRequestMerge(apiClient, baseRepo, pr, api.PullRequestMergeMethodMerge) + } else { + err = fmt.Errorf("unknown merge method (%d) used", mergeMethod) + return err } - if !crossRepoPR { - err = api.BranchDeleteRemote(apiClient, baseRepo, pr.HeadRefName) - var httpErr api.HTTPError - // The ref might have already been deleted by GitHub - if err != nil && (!errors.As(err, &httpErr) || httpErr.StatusCode != 422) { - err = fmt.Errorf("failed to delete remote branch %s: %w", utils.Cyan(pr.HeadRefName), err) - return err - } + if err != nil { + return fmt.Errorf("API call failed: %w", err) } + isTerminal := opts.IO.IsStdoutTTY() + if isTerminal { - fmt.Fprintf(opts.IO.ErrOut, "%s Deleted branch %s%s\n", utils.Red("✔"), utils.Cyan(pr.HeadRefName), branchSwitchString) + fmt.Fprintf(opts.IO.ErrOut, "%s %s pull request #%d (%s)\n", utils.Magenta("✔"), action, pr.Number, pr.Title) } + + if deleteBranch { + branchSwitchString := "" + + if opts.DeleteLocalBranch && !crossRepoPR { + currentBranch, err := opts.Branch() + if err != nil { + return err + } + + var branchToSwitchTo string + if currentBranch == pr.HeadRefName { + branchToSwitchTo, err = api.RepoDefaultBranch(apiClient, baseRepo) + if err != nil { + return err + } + err = git.CheckoutBranch(branchToSwitchTo) + if err != nil { + return err + } + } + + localBranchExists := git.HasLocalBranch(pr.HeadRefName) + if localBranchExists { + err = git.DeleteLocalBranch(pr.HeadRefName) + if err != nil { + err = fmt.Errorf("failed to delete local branch %s: %w", utils.Cyan(pr.HeadRefName), err) + return err + } + } + + if branchToSwitchTo != "" { + branchSwitchString = fmt.Sprintf(" and switched to branch %s", utils.Cyan(branchToSwitchTo)) + } + } + + if !crossRepoPR { + err = api.BranchDeleteRemote(apiClient, baseRepo, pr.HeadRefName) + var httpErr api.HTTPError + // The ref might have already been deleted by GitHub + if err != nil && (!errors.As(err, &httpErr) || httpErr.StatusCode != 422) { + err = fmt.Errorf("failed to delete remote branch %s: %w", utils.Cyan(pr.HeadRefName), err) + return err + } + } + + if isTerminal { + fmt.Fprintf(opts.IO.ErrOut, "%s Deleted branch %s%s\n", utils.Red("✔"), utils.Cyan(pr.HeadRefName), branchSwitchString) + } + } + } else { + fmt.Println("Discarding") } return nil From 05a39b732a331cef78b4bd45063ae4a5c20791a9 Mon Sep 17 00:00:00 2001 From: ShubhankarKG Date: Fri, 4 Sep 2020 18:58:15 +0530 Subject: [PATCH 02/41] Fix tests --- pkg/cmd/pr/merge/merge_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index 2b5f89e7b..19b1d3110 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -197,6 +197,8 @@ func TestPrMerge(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD @@ -242,6 +244,8 @@ func TestPrMerge_nontty(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD @@ -284,6 +288,8 @@ func TestPrMerge_withRepoFlag(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + output, err := runCommand(http, "master", true, "pr merge 1 --merge -R OWNER/REPO") if err != nil { t.Fatalf("error running command `pr merge`: %v", err) @@ -318,6 +324,8 @@ func TestPrMerge_deleteBranch(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git checkout master cs.Stub("") // git rev-parse --verify blueberries` @@ -351,6 +359,9 @@ func TestPrMerge_deleteNonCurrentBranch(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + + prompt.StubConfirm(true) + // We don't expect the default branch to be checked out, just that blueberries is deleted cs.Stub("") // git rev-parse --verify blueberries cs.Stub("") // git branch -d blueberries @@ -384,6 +395,8 @@ func TestPrMerge_noPrNumberGiven(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("branch.blueberries.remote origin\nbranch.blueberries.merge refs/heads/blueberries") // git config --get-regexp ^branch\.master\.(remote|merge) cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD @@ -429,6 +442,8 @@ func TestPrMerge_rebase(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD cs.Stub("") // git checkout master @@ -473,6 +488,8 @@ func TestPrMerge_squash(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD cs.Stub("") // git checkout master @@ -499,6 +516,8 @@ func TestPrMerge_alreadyMerged(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() + prompt.StubConfirm(true) + cs.Stub("") // git config --get-regexp ^branch\.blueberries\.(remote|merge)$ cs.Stub("") // git symbolic-ref --quiet --short HEAD cs.Stub("") // git checkout master @@ -561,6 +580,8 @@ func TestPRMerge_interactive(t *testing.T) { }, }) + prompt.StubConfirm(true) + output, err := runCommand(http, "blueberries", true, "") if err != nil { t.Fatalf("Got unexpected error running `pr merge` %s", err) From 20f88bbd5a3b4706a8f78ed58d41aac1831c5a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 9 Oct 2020 19:06:25 +0200 Subject: [PATCH 03/41] Update references to the `help wanted` label after rename GitHub has some special behavior for the `help wanted` label, but not the `help-wanted` label, so I've renamed ours. Bonus: updated links to docs.github.com --- .github/CONTRIBUTING.md | 25 +++++++++++++++---------- docs/triage.md | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a7889ed07..14c4118c1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,11 +1,5 @@ ## Contributing -[legal]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license -[license]: ../LICENSE -[code-of-conduct]: CODE-OF-CONDUCT.md -[bug issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Abug -[feature request issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement - Hi! Thanks for your interest in contributing to the GitHub CLI! We accept pull requests for bug fixes and features where we've discussed the approach in an issue and given the go-ahead for a community member to work on it. We'd also love to hear about ideas for new features as issues. @@ -17,7 +11,7 @@ Please do: * Open an issue to propose a significant change. * Open a pull request to fix a bug. * Open a pull request to fix documentation about a command. -* Open a pull request for an issue with the `help-wanted` label and leave a comment claiming it. +* Open a pull request for an issue with the [`help wanted`][] label and leave a comment claiming it. Please avoid: @@ -52,6 +46,17 @@ We generate manual pages from source on every release. You do not need to submit ## Resources -- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) -- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) -- [GitHub Help](https://help.github.com) +- [How to Contribute to Open Source][] +- [Using Pull Requests][] +- [GitHub Help][] + + +[bug issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Abug +[feature request issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement +[help wanted]: https://github.com/cli/cli/labels/help%20wanted +[legal]: https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-terms-of-service#6-contributions-under-repository-license +[license]: ../LICENSE +[code-of-conduct]: ./CODE-OF-CONDUCT.md +[How to Contribute to Open Source]: https://opensource.guide/how-to-contribute/ +[Using Pull Requests]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests +[GitHub Help]: https://docs.github.com/ diff --git a/docs/triage.md b/docs/triage.md index d8aa83343..50d5e89f7 100644 --- a/docs/triage.md +++ b/docs/triage.md @@ -28,7 +28,7 @@ just imagine a flowchart - do we want someone in the community to do it? - e.g. the task is relatively straightforward, but no people on our team have the bandwidth to take it on at the moment - ensure that the thread contains all the context necessary for someone new to pick this up - - add `help-wanted` label + - add `help wanted` label - do we want to do it, but not in the next year? - comment acknowledging it, but that we don't plan on working on it this year. - add `future` label From c38003d78b5f9ed793b1c4a7198cc5f175765272 Mon Sep 17 00:00:00 2001 From: Waldir Pimenta Date: Sun, 11 Oct 2020 10:57:04 +0100 Subject: [PATCH 04/41] Improve installation instructions - Mention downloadable binaries for macOS and Linux - Mention Homebrew for Linux - Cleanup whitespace throughout the file --- README.md | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9c3e25bb2..579291c1d 100644 --- a/README.md +++ b/README.md @@ -8,43 +8,38 @@ GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterprise Server 2.20+, and to install on macOS, Windows, and Linux. - ## Documentation Read the [official docs][] for usage and more information. - - ## We want your feedback We'd love to hear your feedback about `gh`. If you spot bugs or have features that you'd really like to see in `gh`, please check out the [contributing page][]. - - ## Installation ### macOS -`gh` is available via [Homebrew][] and [MacPorts][]. +`gh` is available via [Homebrew][] and [MacPorts][], and a downloadable binary from the [releases page][]. #### Homebrew -|Install:|Upgrade:| -|---|---| -|`brew install gh`|`brew upgrade gh`| +| Install: | Upgrade: | +| ----------------- | ----------------- | +| `brew install gh` | `brew upgrade gh` | #### MacPorts -|Install:|Upgrade:| -|---|---| -|`sudo port install gh`|`sudo port selfupdate && sudo port upgrade gh`| - - +| Install: | Upgrade: | +| ---------------------- | ---------------------------------------------- | +| `sudo port install gh` | `sudo port selfupdate && sudo port upgrade gh` | ### Linux -See [Linux installation docs](./docs/install_linux.md). +`gh` is available via [Homebrew](#homebrew), and as downloadable binaries (for `i386`, `amd64` and `arm64`) from the [releases page][]. + +For distro-specific installation instructions, see the [Linux installation docs](./docs/install_linux.md). ### Windows @@ -67,10 +62,9 @@ scoop update gh #### Chocolatey -|Install:|Upgrade:| -|---|---| -|`choco install gh`|`choco upgrade gh`| - +| Install: | Upgrade: | +| ------------------ | ------------------ | +| `choco install gh` | `choco upgrade gh` | #### Signed MSI From 2bdfc10e0ad3e7fdd45338f518691742736b493e Mon Sep 17 00:00:00 2001 From: rista404 Date: Sun, 11 Oct 2020 12:55:34 +0200 Subject: [PATCH 05/41] Add ability to print auth token --- pkg/cmd/auth/status/status.go | 11 ++++- pkg/cmd/auth/status/status_test.go | 64 ++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/pkg/cmd/auth/status/status.go b/pkg/cmd/auth/status/status.go index 78b4b074e..442b84afb 100644 --- a/pkg/cmd/auth/status/status.go +++ b/pkg/cmd/auth/status/status.go @@ -19,7 +19,8 @@ type StatusOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) - Hostname string + Hostname string + ShowToken bool } func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command { @@ -48,6 +49,7 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co } cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "Check a specific hostname's auth status") + cmd.Flags().BoolVarP(&opts.ShowToken, "show-token", "t", false, "Display the auth token") return cmd } @@ -84,7 +86,7 @@ func statusRun(opts *StatusOptions) error { continue } - _, tokenSource, _ := cfg.GetWithSource(hostname, "oauth_token") + token, tokenSource, _ := cfg.GetWithSource(hostname, "oauth_token") tokenIsWriteable := cfg.CheckWriteable(hostname, "oauth_token") == nil statusInfo[hostname] = []string{} @@ -124,6 +126,11 @@ func statusRun(opts *StatusOptions) error { addMsg("%s Git operations for %s configured to use %s protocol.", utils.GreenCheck(), hostname, utils.Bold(proto)) } + tokenDisplay := "*******************" + if opts.ShowToken { + tokenDisplay = token + } + addMsg("%s Token: %s", utils.GreenCheck(), tokenDisplay) } addMsg("") diff --git a/pkg/cmd/auth/status/status_test.go b/pkg/cmd/auth/status/status_test.go index 9aabf4fef..0de14d388 100644 --- a/pkg/cmd/auth/status/status_test.go +++ b/pkg/cmd/auth/status/status_test.go @@ -34,6 +34,13 @@ func Test_NewCmdStatus(t *testing.T) { Hostname: "ellie.williams", }, }, + { + name: "show token", + cli: "--show-token", + wants: StatusOptions{ + ShowToken: true, + }, + }, } for _, tt := range tests { @@ -74,23 +81,6 @@ func Test_statusRun(t *testing.T) { wantErr *regexp.Regexp wantErrOut *regexp.Regexp }{ - { - name: "hostname set", - opts: &StatusOptions{ - Hostname: "joel.miller", - }, - cfg: func(c config.Config) { - _ = c.Set("joel.miller", "oauth_token", "abc123") - _ = c.Set("github.com", "oauth_token", "abc123") - }, - httpStubs: func(reg *httpmock.Registry) { - reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,")) - reg.Register( - httpmock.GraphQL(`query UserCurrent\b`), - httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`)) - }, - wantErrOut: regexp.MustCompile(`Logged in to joel.miller as.*tess`), - }, { name: "hostname set", opts: &StatusOptions{ @@ -161,6 +151,46 @@ func Test_statusRun(t *testing.T) { }, wantErrOut: regexp.MustCompile(`(?s)Logged in to github.com as.*tess.*Logged in to joel.miller as.*tess`), }, + { + name: "hide token", + opts: &StatusOptions{}, + cfg: func(c config.Config) { + _ = c.Set("joel.miller", "oauth_token", "abc123") + _ = c.Set("github.com", "oauth_token", "xyz456") + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,")) + reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,")) + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`)) + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`)) + }, + wantErrOut: regexp.MustCompile(`(?s)Token: \*{19}.*Token: \*{19}`), + }, + { + name: "show token", + opts: &StatusOptions{ + ShowToken: true, + }, + cfg: func(c config.Config) { + _ = c.Set("joel.miller", "oauth_token", "abc123") + _ = c.Set("github.com", "oauth_token", "xyz456") + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register(httpmock.REST("GET", "api/v3/"), httpmock.ScopesResponder("repo,read:org,")) + reg.Register(httpmock.REST("GET", ""), httpmock.ScopesResponder("repo,read:org,")) + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`)) + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data":{"viewer":{"login":"tess"}}}`)) + }, + wantErrOut: regexp.MustCompile(`(?s)Token: xyz456.*Token: abc123`), + }, } for _, tt := range tests { From d29fc16df9cbeb52567e87fdc42ec1c43058e713 Mon Sep 17 00:00:00 2001 From: Waldir Pimenta Date: Mon, 12 Oct 2020 11:57:43 +0100 Subject: [PATCH 06/41] Suggested changes from PR review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 579291c1d..bf8f74799 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ We'd love to hear your feedback about `gh`. If you spot bugs or have features th ### Linux -`gh` is available via [Homebrew](#homebrew), and as downloadable binaries (for `i386`, `amd64` and `arm64`) from the [releases page][]. +`gh` is available via [Homebrew](#homebrew), and as downloadable binaries from the [releases page][]. -For distro-specific installation instructions, see the [Linux installation docs](./docs/install_linux.md). +For more information and distro-specific instructions, see the [Linux installation docs](./docs/install_linux.md). ### Windows From 10e1c5260c6e728b438b66a679be9449f97fccab Mon Sep 17 00:00:00 2001 From: Waldir Pimenta Date: Mon, 12 Oct 2020 13:10:52 +0100 Subject: [PATCH 07/41] Suggested changes from PR review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf8f74799..e0600a4fd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Availability -GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterprise Server 2.20+, and to install on macOS, Windows, and Linux. +GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterprise Server 2.20+, and to install on macOS, Windows, and Linux. ## Documentation @@ -21,7 +21,7 @@ We'd love to hear your feedback about `gh`. If you spot bugs or have features th ### macOS -`gh` is available via [Homebrew][] and [MacPorts][], and a downloadable binary from the [releases page][]. +`gh` is available via [Homebrew][], [MacPorts][], and as a downloadable binary from the [releases page][]. #### Homebrew From 487b62d3b9c2da18b0ba296970fd101309a3a253 Mon Sep 17 00:00:00 2001 From: Jonathan Lloyd Date: Mon, 12 Oct 2020 23:39:18 +0100 Subject: [PATCH 08/41] Clone repos using canonical username/repo name capitalization GitHub user/repo names are case insentitive. This can cause issues when dealing with repos programmatically using systems that aren't aware of this (e.g. go packages). This commit updates the cloning logic of the CLI to query the API for the canonical capitalization and uses that for the clone operation. Fixes: #1845 --- pkg/cmd/repo/clone/clone.go | 67 +++++++++++++++++++++----------- pkg/cmd/repo/clone/clone_test.go | 37 +++++++++++++++++- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/pkg/cmd/repo/clone/clone.go b/pkg/cmd/repo/clone/clone.go index 239fbc21c..08b1cc13b 100644 --- a/pkg/cmd/repo/clone/clone.go +++ b/pkg/cmd/repo/clone/clone.go @@ -82,44 +82,65 @@ func cloneRun(opts *CloneOptions) error { } apiClient := api.NewClientFromHTTP(httpClient) - cloneURL := opts.Repository - if !strings.Contains(cloneURL, ":") { - if !strings.Contains(cloneURL, "/") { + + respositoryIsURL := strings.Contains(opts.Repository, ":") + repositoryIsFullName := !respositoryIsURL && strings.Contains(opts.Repository, "/") + + var repo ghrepo.Interface + var protocol string + if respositoryIsURL { + repoURL, err := git.ParseURL(opts.Repository) + if err != nil { + return err + } + repo, err = ghrepo.FromURL(repoURL) + if err != nil { + return err + } + if repoURL.Scheme == "git+ssh" { + repoURL.Scheme = "ssh" + } + protocol = repoURL.Scheme + } else { + var fullName string + if repositoryIsFullName { + fullName = opts.Repository + } else { currentUser, err := api.CurrentLoginName(apiClient, ghinstance.OverridableDefault()) if err != nil { return err } - cloneURL = currentUser + "/" + cloneURL + fullName = currentUser + "/" + opts.Repository } - repo, err := ghrepo.FromFullName(cloneURL) + + repo, err = ghrepo.FromFullName(fullName) if err != nil { return err } - protocol, err := cfg.Get(repo.RepoHost(), "git_protocol") + protocol, err = cfg.Get(repo.RepoHost(), "git_protocol") if err != nil { return err } - cloneURL = ghrepo.FormatRemoteURL(repo, protocol) } - var repo ghrepo.Interface + // Load the repo from the API to get the username/repo name in its + // canonical capitalization + var canonicalRepo ghrepo.Interface + canonicalRepo, err = api.GitHubRepo(apiClient, repo) + if err != nil { + return err + } + canonicalCloneURL := ghrepo.FormatRemoteURL(canonicalRepo, protocol) + + cloneDir, err := git.RunClone(canonicalCloneURL, opts.GitArgs) + if err != nil { + return err + } + + // If the repo is a fork, add the parent as an upstream var parentRepo ghrepo.Interface - - // TODO: consider caching and reusing `git.ParseSSHConfig().Translator()` - // here to handle hostname aliases in SSH remotes - if u, err := git.ParseURL(cloneURL); err == nil { - repo, _ = ghrepo.FromURL(u) - } - - if repo != nil { - parentRepo, err = api.RepoParent(apiClient, repo) - if err != nil { - return err - } - } - - cloneDir, err := git.RunClone(cloneURL, opts.GitArgs) + parentRepo, err = api.RepoParent(apiClient, canonicalRepo) if err != nil { return err } diff --git a/pkg/cmd/repo/clone/clone_test.go b/pkg/cmd/repo/clone/clone_test.go index 54aa2f1cb..8fedca21a 100644 --- a/pkg/cmd/repo/clone/clone_test.go +++ b/pkg/cmd/repo/clone/clone_test.go @@ -77,17 +77,32 @@ func Test_RepoClone(t *testing.T) { { name: "HTTPS URL", args: "https://github.com/OWNER/REPO", - want: "git clone https://github.com/OWNER/REPO", + want: "git clone https://github.com/OWNER/REPO.git", }, { name: "SSH URL", args: "git@github.com:OWNER/REPO.git", want: "git clone git@github.com:OWNER/REPO.git", }, + { + name: "Non-canonical capitalization", + args: "git@github.com:Ower/Repo.git", + want: "git clone git@github.com:OWNER/REPO.git", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reg := &httpmock.Registry{} + reg.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "name": "REPO", + "owner": { + "login": "OWNER" + } + } } } + `)) reg.Register( httpmock.GraphQL(`query RepositoryFindParent\b`), httpmock.StringResponse(` @@ -119,6 +134,16 @@ func Test_RepoClone(t *testing.T) { func Test_RepoClone_hasParent(t *testing.T) { reg := &httpmock.Registry{} + reg.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "name": "REPO", + "owner": { + "login": "OWNER" + } + } } } + `)) reg.Register( httpmock.GraphQL(`query RepositoryFindParent\b`), httpmock.StringResponse(` @@ -155,6 +180,16 @@ func Test_RepoClone_withoutUsername(t *testing.T) { { "data": { "viewer": { "login": "OWNER" }}}`)) + reg.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "name": "REPO", + "owner": { + "login": "OWNER" + } + } } } + `)) reg.Register( httpmock.GraphQL(`query RepositoryFindParent\b`), httpmock.StringResponse(` From a5ec03d00094950684828787dc21f95d8ee6418a Mon Sep 17 00:00:00 2001 From: Jonathan Lloyd Date: Mon, 12 Oct 2020 23:56:51 +0100 Subject: [PATCH 09/41] Improve test args --- pkg/cmd/repo/clone/clone_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/repo/clone/clone_test.go b/pkg/cmd/repo/clone/clone_test.go index 8fedca21a..875183626 100644 --- a/pkg/cmd/repo/clone/clone_test.go +++ b/pkg/cmd/repo/clone/clone_test.go @@ -86,8 +86,8 @@ func Test_RepoClone(t *testing.T) { }, { name: "Non-canonical capitalization", - args: "git@github.com:Ower/Repo.git", - want: "git clone git@github.com:OWNER/REPO.git", + args: "Owner/Repo", + want: "git clone https://github.com/OWNER/REPO.git", }, } for _, tt := range tests { From d4e6fc11d73528a2fa88307cf0de0f638c0975fb Mon Sep 17 00:00:00 2001 From: zamasu Date: Tue, 13 Oct 2020 08:36:47 +0900 Subject: [PATCH 10/41] Add int32 validation to milestone numbers --- api/queries_issue.go | 4 ++-- api/queries_repo.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/queries_issue.go b/api/queries_issue.go index d607901a3..3cfd853bd 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -251,8 +251,8 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str if milestoneString != "" { var milestone *RepoMilestone - if milestoneNumber, err := strconv.Atoi(milestoneString); err == nil { - milestone, err = MilestoneByNumber(client, repo, milestoneNumber) + if milestoneNumber, err := strconv.ParseInt(milestoneString, 10, 32); err == nil { + milestone, err = MilestoneByNumber(client, repo, int32(milestoneNumber)) if err != nil { return nil, err } diff --git a/api/queries_repo.go b/api/queries_repo.go index fdbd13e92..9fd8b05bb 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -840,7 +840,7 @@ func MilestoneByTitle(client *Client, repo ghrepo.Interface, title string) (*Rep return nil, fmt.Errorf("no milestone found with title %q", title) } -func MilestoneByNumber(client *Client, repo ghrepo.Interface, number int) (*RepoMilestone, error) { +func MilestoneByNumber(client *Client, repo ghrepo.Interface, number int32) (*RepoMilestone, error) { var query struct { Repository struct { Milestone *RepoMilestone `graphql:"milestone(number: $number)"` From bb6a571ab5dd89b98eb3edecebc77b4a399b9d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 13 Oct 2020 16:40:40 +0200 Subject: [PATCH 11/41] Tweak contributing guidelines --- .github/CONTRIBUTING.md | 3 ++- README.md | 18 ++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 14c4118c1..d43f355f2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,7 +11,7 @@ Please do: * Open an issue to propose a significant change. * Open a pull request to fix a bug. * Open a pull request to fix documentation about a command. -* Open a pull request for an issue with the [`help wanted`][] label and leave a comment claiming it. +* Open a pull request for any issue with [`help wanted`][] or [`good first issue`][] labels. Please avoid: @@ -54,6 +54,7 @@ We generate manual pages from source on every release. You do not need to submit [bug issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Abug [feature request issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement [help wanted]: https://github.com/cli/cli/labels/help%20wanted +[good first issue]: https://github.com/cli/cli/labels/good%20first%20issue [legal]: https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-terms-of-service#6-contributions-under-repository-license [license]: ../LICENSE [code-of-conduct]: ./CODE-OF-CONDUCT.md diff --git a/README.md b/README.md index 9c3e25bb2..c8aae9cef 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,15 @@ ![screenshot of gh pr status](https://user-images.githubusercontent.com/98482/84171218-327e7a80-aa40-11ea-8cd1-5177fc2d0e72.png) -## Availability - -GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterprise Server 2.20+, and to install on macOS, Windows, and Linux. - +GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterprise Server 2.20+, and to install on macOS, Windows, and Linux. ## Documentation -Read the [official docs][] for usage and more information. +[See the manual][manual] for setup and usage instructions. +## Contributing - -## We want your feedback - -We'd love to hear your feedback about `gh`. If you spot bugs or have features that you'd really like to see in `gh`, please check out the [contributing page][]. - +If anything feels off, or if you feel that some functionality is missing, please check out the [contributing page][contributing]. We'd love to hear your feedback! @@ -92,13 +86,13 @@ tools bring GitHub to the terminal, `hub` behaves as a proxy to `git`, and `gh` tool. Check out our [more detailed explanation][gh-vs-hub] to learn more. -[official docs]: https://cli.github.com/manual +[manual]: https://cli.github.com/manual/ [Homebrew]: https://brew.sh [MacPorts]: https://www.macports.org [scoop]: https://scoop.sh [Chocolatey]: https://chocolatey.org [releases page]: https://github.com/cli/cli/releases/latest [hub]: https://github.com/github/hub -[contributing page]: https://github.com/cli/cli/blob/trunk/.github/CONTRIBUTING.md +[contributing]: ./.github/CONTRIBUTING.md [gh-vs-hub]: ./docs/gh-vs-hub.md [build from source]: ./docs/source.md From b3f64acc55bc2267cae840d182806d2a08f0d87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 13 Oct 2020 17:44:13 +0200 Subject: [PATCH 12/41] Clarify contributions section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a52280cae..e327f5490 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterpr ## Contributing -If anything feels off, or if you feel that some functionality is missing, please check out the [contributing page][contributing]. +If anything feels off, or if you feel that some functionality is missing, please check out the [contributing page][contributing]. There you will find instructions for sharing your feedback, building the tool locally, and submitting pull requests to the project. From ef907463d94c1cdde745ae40a1e7fc0b2703f2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 13 Oct 2020 18:05:27 +0200 Subject: [PATCH 13/41] Fix label links --- .github/CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d43f355f2..3bb884d84 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,7 +11,7 @@ Please do: * Open an issue to propose a significant change. * Open a pull request to fix a bug. * Open a pull request to fix documentation about a command. -* Open a pull request for any issue with [`help wanted`][] or [`good first issue`][] labels. +* Open a pull request for any issue labelled [`help wanted`][hw] or [`good first issue`][gfi]. Please avoid: @@ -53,8 +53,8 @@ We generate manual pages from source on every release. You do not need to submit [bug issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Abug [feature request issues]: https://github.com/cli/cli/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement -[help wanted]: https://github.com/cli/cli/labels/help%20wanted -[good first issue]: https://github.com/cli/cli/labels/good%20first%20issue +[hw]: https://github.com/cli/cli/labels/help%20wanted +[gfi]: https://github.com/cli/cli/labels/good%20first%20issue [legal]: https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-terms-of-service#6-contributions-under-repository-license [license]: ../LICENSE [code-of-conduct]: ./CODE-OF-CONDUCT.md From d66fd2fe9e41293d1bcd4f91d48fb5cf8fcb773b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 13 Oct 2020 18:06:00 +0200 Subject: [PATCH 14/41] Remove `needs-user-input` from "please avoid" The user input might have come in a reply already, but the maintainers haven't gotten around to remove the stale label yet. This shouldn't prevent someone from submitting a PR. --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3bb884d84..c15d1e8b0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,7 +15,7 @@ Please do: Please avoid: -* Opening pull requests for issues marked `needs-design`, `needs-investigation`, `needs-user-input`, or `blocked`. +* Opening pull requests for issues marked `needs-design`, `needs-investigation`, or `blocked`. * Adding installation instructions specifically for your OS/package manager. * Opening pull requests for any issue marked `core`. These issues require additional context from the core CLI team at GitHub and any external pull requests will not be accepted. From bd25dae5e0e150a7f53f2bd7683af104dd9035f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 13 Oct 2020 18:07:05 +0200 Subject: [PATCH 15/41] Update triaging docs --- docs/triage.md | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/docs/triage.md b/docs/triage.md index 50d5e89f7..a37f55a30 100644 --- a/docs/triage.md +++ b/docs/triage.md @@ -16,51 +16,40 @@ triage day. # Incoming issues -just imagine a flowchart - - can this be closed outright? - e.g. spam/junk - close without comment - do we not want to do it? - e.g. have already discussed not wanting to do or duplicate issue - - comment acknowledging receipt - - close + - comment and close - do we want someone in the community to do it? - e.g. the task is relatively straightforward, but no people on our team have the bandwidth to take it on at the moment - ensure that the thread contains all the context necessary for someone new to pick this up - add `help wanted` label -- do we want to do it, but not in the next year? - - comment acknowledging it, but that we don't plan on working on it this year. - - add `future` label - - add additional labels as needed(examples include `enhancement` or `bug`) - - close + - consider adding `good first issue` label - do we want to do it? - - e.g. bugs or things we have discussed before - comment acknowledging it - label appropriately - - add to project TODO column if appropriate, otherwise just leave it labeled -- is it intriguing but needs discussion? + - add to project TODO column if this is something that should ship soon +- is it intriguing, but requires discussion? - label `needs-design` if design input is needed, ping - label `needs-investigation` if engineering research is required before action can be taken - - ping engineers if eng needed - - ping product if it's about future directions/roadmap/big changes - does it need more info from the issue author? - - ask the user for that + - ask the user for details - add `needs-user-input` label -- is it a user asking for help and you have all the info you need to help? - - try and help +- is it a usage/support question? + - offer some instructions/workaround and close # Incoming PRs just imagine a flowchart - can it be closed outright? - - ie spam/junk -- do we not want to do it? - - ie have already discussed not wanting to do, duplicate issue - - comment acknowledging receipt + - e.g. spam/junk - close -- is it intriguing but needs discussion? +- do we not want to do it? + - comment and close +- is it intriguing, but requires discussion and there is no referenced issue? - request an issue - close - is it something we want to include? @@ -82,7 +71,7 @@ For each PR, ask: ## Examples -We want the cli/cli repo to be a safe and encouraging open-source environment. Below are some examples +We want our project to be a safe and encouraging open-source environment. Below are some examples of how to empathetically respond to or close an issue/PR: - [Closing a quality PR its scope is too large](https://github.com/cli/cli/pull/1161) From 8f44aee76a683f996d44974a2b1daaae2cec48cf Mon Sep 17 00:00:00 2001 From: Jonathan Lloyd Date: Tue, 13 Oct 2020 20:41:16 +0100 Subject: [PATCH 16/41] Load repo and parent in single query --- api/queries_repo.go | 23 +++++++++++++++-------- pkg/cmd/repo/clone/clone.go | 15 ++++----------- pkg/cmd/repo/clone/clone_test.go | 23 ++++++----------------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/api/queries_repo.go b/api/queries_repo.go index fdbd13e92..2e000b979 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -88,16 +88,23 @@ func (r Repository) ViewerCanTriage() bool { func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) { query := ` + fragment repo on Repository { + id + name + owner { login } + hasIssuesEnabled + description + viewerPermission + defaultBranchRef { + name + } + } + query RepositoryInfo($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { - id - name - owner { login } - hasIssuesEnabled - description - viewerPermission - defaultBranchRef { - name + ...repo + parent { + ...repo } } }` diff --git a/pkg/cmd/repo/clone/clone.go b/pkg/cmd/repo/clone/clone.go index 08b1cc13b..6dc225538 100644 --- a/pkg/cmd/repo/clone/clone.go +++ b/pkg/cmd/repo/clone/clone.go @@ -126,8 +126,7 @@ func cloneRun(opts *CloneOptions) error { // Load the repo from the API to get the username/repo name in its // canonical capitalization - var canonicalRepo ghrepo.Interface - canonicalRepo, err = api.GitHubRepo(apiClient, repo) + canonicalRepo, err := api.GitHubRepo(apiClient, repo) if err != nil { return err } @@ -139,18 +138,12 @@ func cloneRun(opts *CloneOptions) error { } // If the repo is a fork, add the parent as an upstream - var parentRepo ghrepo.Interface - parentRepo, err = api.RepoParent(apiClient, canonicalRepo) - if err != nil { - return err - } - - if parentRepo != nil { - protocol, err := cfg.Get(parentRepo.RepoHost(), "git_protocol") + if canonicalRepo.Parent != nil { + protocol, err := cfg.Get(canonicalRepo.Parent.RepoHost(), "git_protocol") if err != nil { return err } - upstreamURL := ghrepo.FormatRemoteURL(parentRepo, protocol) + upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol) err = git.AddUpstreamRemote(upstreamURL, cloneDir) if err != nil { diff --git a/pkg/cmd/repo/clone/clone_test.go b/pkg/cmd/repo/clone/clone_test.go index 875183626..f696cd354 100644 --- a/pkg/cmd/repo/clone/clone_test.go +++ b/pkg/cmd/repo/clone/clone_test.go @@ -103,13 +103,6 @@ func Test_RepoClone(t *testing.T) { } } } } `)) - reg.Register( - httpmock.GraphQL(`query RepositoryFindParent\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "parent": null - } } } - `)) httpClient := &http.Client{Transport: reg} @@ -141,19 +134,15 @@ func Test_RepoClone_hasParent(t *testing.T) { "name": "REPO", "owner": { "login": "OWNER" + }, + "parent": { + "name": "ORIG", + "owner": { + "login": "hubot" + } } } } } `)) - reg.Register( - httpmock.GraphQL(`query RepositoryFindParent\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "parent": { - "owner": {"login": "hubot"}, - "name": "ORIG" - } - } } } - `)) httpClient := &http.Client{Transport: reg} From a90b62fb6c497b74826d9961010136dd77f474a7 Mon Sep 17 00:00:00 2001 From: zamasu Date: Sun, 11 Oct 2020 17:42:11 +0900 Subject: [PATCH 17/41] Fix parsing labels in paginated IssueList --- api/queries_issue.go | 7 +++- api/queries_issue_test.go | 82 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/api/queries_issue.go b/api/queries_issue.go index d607901a3..fc88d1c16 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -270,7 +270,7 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str variables["milestone"] = milestoneRESTID } - var response struct { + type responseData struct { Repository struct { Issues struct { TotalCount int @@ -285,10 +285,12 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str } var issues []Issue + var totalCount int pageLimit := min(limit, 100) loop: for { + var response responseData variables["limit"] = pageLimit err := client.GraphQL(repo.RepoHost(), query, variables, &response) if err != nil { @@ -297,6 +299,7 @@ loop: if !response.Repository.HasIssuesEnabled { return nil, fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(repo)) } + totalCount = response.Repository.Issues.TotalCount for _, issue := range response.Repository.Issues.Nodes { issues = append(issues, issue) @@ -313,7 +316,7 @@ loop: } } - res := IssuesAndTotalCount{Issues: issues, TotalCount: response.Repository.Issues.TotalCount} + res := IssuesAndTotalCount{Issues: issues, TotalCount: totalCount} return &res, nil } diff --git a/api/queries_issue_test.go b/api/queries_issue_test.go index 2ea64f0c6..32e757efd 100644 --- a/api/queries_issue_test.go +++ b/api/queries_issue_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "reflect" "testing" "github.com/cli/cli/internal/ghrepo" @@ -68,3 +69,84 @@ func TestIssueList(t *testing.T) { t.Errorf("expected %q, got %q", "ENDCURSOR", endCursor) } } + +func TestIssueList_pagination(t *testing.T) { + http := &httpmock.Registry{} + client := NewClient(ReplaceTripper(http)) + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "repository": { + "hasIssuesEnabled": true, + "issues": { + "nodes": [ + { + "title": "issue1", + "labels": { "nodes": [ { "name": "bug" } ], "totalCount": 1 }, + "assignees": { "nodes": [ { "login": "user1" } ], "totalCount": 1 } + } + ], + "pageInfo": { + "hasNextPage": true, + "endCursor": "ENDCURSOR" + }, + "totalCount": 2 + } + } } } + `)) + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "repository": { + "hasIssuesEnabled": true, + "issues": { + "nodes": [ + { + "title": "issue2", + "labels": { "nodes": [ { "name": "enhancement" } ], "totalCount": 1 }, + "assignees": { "nodes": [ { "login": "user2" } ], "totalCount": 1 } + } + ], + "pageInfo": { + "hasNextPage": false, + "endCursor": "ENDCURSOR" + }, + "totalCount": 2 + } + } } } + `)) + repo := ghrepo.New("OWNER", "REPO") + + want := &IssuesAndTotalCount{ + Issues: []Issue{ + { + Title: "issue1", + Assignees: struct { + Nodes []struct{ Login string } + TotalCount int + }{[]struct{ Login string }{{"user1"}}, 1}, + Labels: struct { + Nodes []struct{ Name string } + TotalCount int + }{[]struct{ Name string }{{"bug"}}, 1}, + }, + { + Title: "issue2", + Assignees: struct { + Nodes []struct{ Login string } + TotalCount int + }{[]struct{ Login string }{{"user2"}}, 1}, + Labels: struct { + Nodes []struct{ Name string } + TotalCount int + }{[]struct{ Name string }{{"enhancement"}}, 1}, + }, + }, + TotalCount: 2, + } + + got, err := IssueList(client, repo, "", nil, "", 0, "", "", "") + if err != nil { + t.Errorf("IssueList() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("IssueList() = %v, want %v", got, want) + } +} From 813fbc9b8da7eca89f931a38b9c4e5b6103b85e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 14 Oct 2020 15:38:21 +0200 Subject: [PATCH 18/41] Ensure that we don't reuse the same deserialization struct over pagination iterations This is to avoid subtle deserialization issues in nested slices. --- api/queries_org.go | 6 ++++-- api/queries_repo.go | 12 ++++++++---- pkg/cmd/release/list/http.go | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/api/queries_org.go b/api/queries_org.go index f3744e147..a47963aea 100644 --- a/api/queries_org.go +++ b/api/queries_org.go @@ -9,7 +9,7 @@ import ( // OrganizationProjects fetches all open projects for an organization func OrganizationProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, error) { - var query struct { + type responseData struct { Organization struct { Projects struct { Nodes []RepoProject @@ -30,6 +30,7 @@ func OrganizationProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, var projects []RepoProject for { + var query responseData err := gql.QueryNamed(context.Background(), "OrganizationProjectList", &query, variables) if err != nil { return nil, err @@ -52,7 +53,7 @@ type OrgTeam struct { // OrganizationTeams fetches all the teams in an organization func OrganizationTeams(client *Client, repo ghrepo.Interface) ([]OrgTeam, error) { - var query struct { + type responseData struct { Organization struct { Teams struct { Nodes []OrgTeam @@ -73,6 +74,7 @@ func OrganizationTeams(client *Client, repo ghrepo.Interface) ([]OrgTeam, error) var teams []OrgTeam for { + var query responseData err := gql.QueryNamed(context.Background(), "OrganizationTeamList", &query, variables) if err != nil { return nil, err diff --git a/api/queries_repo.go b/api/queries_repo.go index fdbd13e92..54194b1bb 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -657,7 +657,7 @@ type RepoProject struct { // RepoProjects fetches all open projects for a repository func RepoProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, error) { - var query struct { + type responseData struct { Repository struct { Projects struct { Nodes []RepoProject @@ -679,6 +679,7 @@ func RepoProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, error) var projects []RepoProject for { + var query responseData err := gql.QueryNamed(context.Background(), "RepositoryProjectList", &query, variables) if err != nil { return nil, err @@ -701,7 +702,7 @@ type RepoAssignee struct { // RepoAssignableUsers fetches all the assignable users for a repository func RepoAssignableUsers(client *Client, repo ghrepo.Interface) ([]RepoAssignee, error) { - var query struct { + type responseData struct { Repository struct { AssignableUsers struct { Nodes []RepoAssignee @@ -723,6 +724,7 @@ func RepoAssignableUsers(client *Client, repo ghrepo.Interface) ([]RepoAssignee, var users []RepoAssignee for { + var query responseData err := gql.QueryNamed(context.Background(), "RepositoryAssignableUsers", &query, variables) if err != nil { return nil, err @@ -745,7 +747,7 @@ type RepoLabel struct { // RepoLabels fetches all the labels in a repository func RepoLabels(client *Client, repo ghrepo.Interface) ([]RepoLabel, error) { - var query struct { + type responseData struct { Repository struct { Labels struct { Nodes []RepoLabel @@ -767,6 +769,7 @@ func RepoLabels(client *Client, repo ghrepo.Interface) ([]RepoLabel, error) { var labels []RepoLabel for { + var query responseData err := gql.QueryNamed(context.Background(), "RepositoryLabelList", &query, variables) if err != nil { return nil, err @@ -789,7 +792,7 @@ type RepoMilestone struct { // RepoMilestones fetches all open milestones in a repository func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, error) { - var query struct { + type responseData struct { Repository struct { Milestones struct { Nodes []RepoMilestone @@ -811,6 +814,7 @@ func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, err var milestones []RepoMilestone for { + var query responseData err := gql.QueryNamed(context.Background(), "RepositoryMilestoneList", &query, variables) if err != nil { return nil, err diff --git a/pkg/cmd/release/list/http.go b/pkg/cmd/release/list/http.go index 763963ff1..8aae95674 100644 --- a/pkg/cmd/release/list/http.go +++ b/pkg/cmd/release/list/http.go @@ -21,7 +21,7 @@ type Release struct { } func fetchReleases(httpClient *http.Client, repo ghrepo.Interface, limit int) ([]Release, error) { - var query struct { + type responseData struct { Repository struct { Releases struct { Nodes []Release @@ -50,6 +50,7 @@ func fetchReleases(httpClient *http.Client, repo ghrepo.Interface, limit int) ([ var releases []Release loop: for { + var query responseData err := gql.QueryNamed(context.Background(), "RepositoryReleaseList", &query, variables) if err != nil { return nil, err From e270cdb29e171ac9d5441ed616ad669bd5431ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 14 Oct 2020 15:54:42 +0200 Subject: [PATCH 19/41] Simplify test to avoid use of `reflect` --- api/queries_issue_test.go | 63 +++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/api/queries_issue_test.go b/api/queries_issue_test.go index 32e757efd..83dee55b7 100644 --- a/api/queries_issue_test.go +++ b/api/queries_issue_test.go @@ -4,9 +4,10 @@ import ( "bytes" "encoding/json" "io/ioutil" - "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/pkg/httpmock" ) @@ -73,6 +74,7 @@ func TestIssueList(t *testing.T) { func TestIssueList_pagination(t *testing.T) { http := &httpmock.Registry{} client := NewClient(ReplaceTripper(http)) + http.StubResponse(200, bytes.NewBufferString(` { "data": { "repository": { "hasIssuesEnabled": true, @@ -111,42 +113,33 @@ func TestIssueList_pagination(t *testing.T) { } } } } `)) + repo := ghrepo.New("OWNER", "REPO") - - want := &IssuesAndTotalCount{ - Issues: []Issue{ - { - Title: "issue1", - Assignees: struct { - Nodes []struct{ Login string } - TotalCount int - }{[]struct{ Login string }{{"user1"}}, 1}, - Labels: struct { - Nodes []struct{ Name string } - TotalCount int - }{[]struct{ Name string }{{"bug"}}, 1}, - }, - { - Title: "issue2", - Assignees: struct { - Nodes []struct{ Login string } - TotalCount int - }{[]struct{ Login string }{{"user2"}}, 1}, - Labels: struct { - Nodes []struct{ Name string } - TotalCount int - }{[]struct{ Name string }{{"enhancement"}}, 1}, - }, - }, - TotalCount: 2, - } - - got, err := IssueList(client, repo, "", nil, "", 0, "", "", "") + res, err := IssueList(client, repo, "", nil, "", 0, "", "", "") if err != nil { - t.Errorf("IssueList() error = %v", err) - return + t.Fatalf("IssueList() error = %v", err) } - if !reflect.DeepEqual(got, want) { - t.Errorf("IssueList() = %v, want %v", got, want) + + assert.Equal(t, 2, res.TotalCount) + assert.Equal(t, 2, len(res.Issues)) + + getLabels := func(i Issue) []string { + var labels []string + for _, l := range i.Labels.Nodes { + labels = append(labels, l.Name) + } + return labels } + getAssignees := func(i Issue) []string { + var logins []string + for _, u := range i.Assignees.Nodes { + logins = append(logins, u.Login) + } + return logins + } + + assert.Equal(t, []string{"bug"}, getLabels(res.Issues[0])) + assert.Equal(t, []string{"user1"}, getAssignees(res.Issues[0])) + assert.Equal(t, []string{"enhancement"}, getLabels(res.Issues[1])) + assert.Equal(t, []string{"user2"}, getAssignees(res.Issues[1])) } From 93bc3892697c32ea609ac057f4df22abe3dc468e Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Thu, 15 Oct 2020 22:04:18 +0000 Subject: [PATCH 20/41] Add scopes flag to login --- pkg/cmd/auth/login/login.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index bd02ed9e4..7ded3056b 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -27,6 +27,7 @@ type LoginOptions struct { Interactive bool Hostname string + Scopes []string Token string Web bool } @@ -50,6 +51,9 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm Alternatively, pass in a token on standard input by using %[1]s--with-token%[1]s. The minimum required scopes for the token are: "repo", "read:org". + + The --scopes flag accepts a comma separated list of scopes you want your gh credentials to have. If + absent, this command ensures that gh has access to a minimum set of scopes. `, "`"), Example: heredoc.Doc(` # start interactive setup @@ -104,6 +108,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm } cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance to authenticate with") + cmd.Flags().StringSliceVarP(&opts.Scopes, "scopes", "s", nil, "Additional authentication scopes for gh to have") cmd.Flags().BoolVar(&tokenStdin, "with-token", false, "Read token from standard input") cmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a browser to authenticate") @@ -223,7 +228,7 @@ func loginRun(opts *LoginOptions) error { } if authMode == 0 { - _, err := authflow.AuthFlowWithConfig(cfg, hostname, "", []string{}) + _, err := authflow.AuthFlowWithConfig(cfg, hostname, "", opts.Scopes) if err != nil { return fmt.Errorf("failed to authenticate via web browser: %w", err) } From 49378fb60f8359fc3f2eec8fe35ec6586f844646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 16 Oct 2020 13:20:23 +0000 Subject: [PATCH 21/41] Handle Ctrl-C in repo garden --- pkg/cmd/repo/garden/garden.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 2402ccbc1..01fe07b77 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -7,10 +7,13 @@ import ( "io" "math/rand" "net/http" + "os" "os/exec" + "os/signal" "runtime" "strconv" "strings" + "syscall" "github.com/cli/cli/api" "github.com/cli/cli/internal/ghinstance" @@ -216,6 +219,22 @@ func gardenRun(opts *GardenOptions) error { _ = exec.Command("stty", sttyFileArg, "/dev/tty", "cbreak", "min", "1").Run() _ = exec.Command("stty", sttyFileArg, "/dev/tty", "-echo").Run() + walkAway := func() { + clear(opts.IO) + fmt.Fprint(out, "\033[?25h") + _ = exec.Command("stty", sttyFileArg, "/dev/tty", strings.TrimSpace(string(oldTTYSettings))).Run() + fmt.Fprintln(out) + fmt.Fprintln(out, utils.Bold("You turn and walk away from the wildflower garden...")) + } + + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func(){ + <-c + walkAway() + os.Exit(0) + }() + var b []byte = make([]byte, 3) for { _, _ = opts.IO.In.Read(b) @@ -296,12 +315,7 @@ func gardenRun(opts *GardenOptions) error { fmt.Fprint(out, utils.Bold(sl)) } - clear(opts.IO) - fmt.Fprint(out, "\033[?25h") - _ = exec.Command("stty", sttyFileArg, "/dev/tty", strings.TrimSpace(string(oldTTYSettings))).Run() - fmt.Fprintln(out) - fmt.Fprintln(out, utils.Bold("You turn and walk away from the wildflower garden...")) - + walkAway() return nil } From 7660b66c6cf57c2fa23ff1fe071526dedd86c5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 16 Oct 2020 14:07:01 +0000 Subject: [PATCH 22/41] go fmt --- pkg/cmd/repo/garden/garden.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 01fe07b77..8ab347594 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -229,7 +229,7 @@ func gardenRun(opts *GardenOptions) error { c := make(chan os.Signal) signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func(){ + go func() { <-c walkAway() os.Exit(0) From 02883a89e3a9b28e3de50f4fffecffff93fb7096 Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Fri, 16 Oct 2020 15:58:07 +0000 Subject: [PATCH 23/41] add tests --- pkg/cmd/auth/login/login_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/cmd/auth/login/login_test.go b/pkg/cmd/auth/login/login_test.go index 0abfeb7e3..9a6b5aa8b 100644 --- a/pkg/cmd/auth/login/login_test.go +++ b/pkg/cmd/auth/login/login_test.go @@ -118,6 +118,28 @@ func Test_NewCmdLogin(t *testing.T) { cli: "--web --with-token", wantsErr: true, }, + { + name: "tty one scope", + stdinTTY: true, + cli: "--scopes repo:invite", + wants: LoginOptions{ + Hostname: "", + Scopes: []string{"repo:invite"}, + Token: "", + Interactive: true, + }, + }, + { + name: "tty scopes", + stdinTTY: true, + cli: "--scopes repo:invite,read:public_key", + wants: LoginOptions{ + Hostname: "", + Scopes: []string{"repo:invite", "read:public_key"}, + Token: "", + Interactive: true, + }, + }, } for _, tt := range tests { @@ -160,6 +182,7 @@ func Test_NewCmdLogin(t *testing.T) { assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname) assert.Equal(t, tt.wants.Web, gotOpts.Web) assert.Equal(t, tt.wants.Interactive, gotOpts.Interactive) + assert.Equal(t, tt.wants.Scopes, gotOpts.Scopes) }) } } From 09401b30b6bab7d15034b9294b155afa079648e2 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Thu, 8 Oct 2020 09:39:07 -0300 Subject: [PATCH 24/41] Validate git protocol config before setting it --- internal/config/config_type.go | 28 ++++++++++++++++++++++++++++ internal/config/config_type_test.go | 15 +++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/internal/config/config_type.go b/internal/config/config_type.go index 0fd85d7f0..a42ba87c5 100644 --- a/internal/config/config_type.go +++ b/internal/config/config_type.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strings" "github.com/cli/cli/internal/ghinstance" "gopkg.in/yaml.v3" @@ -16,6 +17,10 @@ const ( PromptsEnabled = "enabled" ) +var configValues = map[string][]string{ + "git_protocol": {"ssh", "https"}, +} + // This interface describes interacting with some persistent configuration for gh. type Config interface { Get(string, string) (string, error) @@ -271,7 +276,30 @@ func (c *fileConfig) GetWithSource(hostname, key string) (string, string, error) return value, defaultSource, nil } +func validConfigValues(key string) []string { + return configValues[key] +} + +func validateConfigEntry(key, value string) error { + validValues := validConfigValues(key) + + if len(validValues) == 0 { + return nil + } + + for _, v := range validValues { + if v == value { + return nil + } + } + + return fmt.Errorf("invalid value. Possible values for \"%s\" are: %s", key, strings.Join(validValues, ", ")) +} + func (c *fileConfig) Set(hostname, key, value string) error { + if err := validateConfigEntry(key, value); err != nil { + return err + } if hostname == "" { return c.SetStringValue(key, value) } else { diff --git a/internal/config/config_type_test.go b/internal/config/config_type_test.go index c9f33b45c..9e7fe573d 100644 --- a/internal/config/config_type_test.go +++ b/internal/config/config_type_test.go @@ -28,6 +28,7 @@ func Test_fileConfig_Set(t *testing.T) { example.com: editor: vim `, hostsBuf.String()) + assert.EqualError(t, c.Set("github.com", "git_protocol", "sshpps"), "invalid value. Possible values for \"git_protocol\" are: ssh, https") } func Test_defaultConfig(t *testing.T) { @@ -68,3 +69,17 @@ func Test_defaultConfig(t *testing.T) { expansion, _ := aliases.Get("co") assert.Equal(t, expansion, "pr checkout") } + +func Test_validateConfigEntry(t *testing.T) { + err := validateConfigEntry("git_protocol", "sshpps") + assert.EqualError(t, err, "invalid value. Possible values for \"git_protocol\" are: ssh, https") + + err = validateConfigEntry("git_protocol", "ssh") + assert.Nil(t, err) + + err = validateConfigEntry("editor", "vim") + assert.Nil(t, err) + + err = validateConfigEntry("got", "123") + assert.Nil(t, err) +} From 14a222bade1a7b3ee4de2169358c0fc4c9f9d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 16 Oct 2020 18:51:00 +0000 Subject: [PATCH 25/41] Let the receiver choose how to format the `cfg.Set()` value error --- internal/config/config_type.go | 16 +++++++++------- internal/config/config_type_test.go | 4 ++-- pkg/cmd/config/config.go | 11 +++++++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/internal/config/config_type.go b/internal/config/config_type.go index a42ba87c5..badf7ee00 100644 --- a/internal/config/config_type.go +++ b/internal/config/config_type.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "sort" - "strings" "github.com/cli/cli/internal/ghinstance" "gopkg.in/yaml.v3" @@ -276,14 +275,17 @@ func (c *fileConfig) GetWithSource(hostname, key string) (string, string, error) return value, defaultSource, nil } -func validConfigValues(key string) []string { - return configValues[key] +type InvalidValueError struct { + ValidValues []string +} + +func (e InvalidValueError) Error() string { + return "invalid value" } func validateConfigEntry(key, value string) error { - validValues := validConfigValues(key) - - if len(validValues) == 0 { + validValues, found := configValues[key] + if !found { return nil } @@ -293,7 +295,7 @@ func validateConfigEntry(key, value string) error { } } - return fmt.Errorf("invalid value. Possible values for \"%s\" are: %s", key, strings.Join(validValues, ", ")) + return &InvalidValueError{ValidValues: validValues} } func (c *fileConfig) Set(hostname, key, value string) error { diff --git a/internal/config/config_type_test.go b/internal/config/config_type_test.go index 9e7fe573d..46b9016f4 100644 --- a/internal/config/config_type_test.go +++ b/internal/config/config_type_test.go @@ -28,7 +28,7 @@ func Test_fileConfig_Set(t *testing.T) { example.com: editor: vim `, hostsBuf.String()) - assert.EqualError(t, c.Set("github.com", "git_protocol", "sshpps"), "invalid value. Possible values for \"git_protocol\" are: ssh, https") + assert.EqualError(t, c.Set("github.com", "git_protocol", "sshpps"), "invalid value") } func Test_defaultConfig(t *testing.T) { @@ -72,7 +72,7 @@ func Test_defaultConfig(t *testing.T) { func Test_validateConfigEntry(t *testing.T) { err := validateConfigEntry("git_protocol", "sshpps") - assert.EqualError(t, err, "invalid value. Possible values for \"git_protocol\" are: ssh, https") + assert.EqualError(t, err, "invalid value") err = validateConfigEntry("git_protocol", "ssh") assert.Nil(t, err) diff --git a/pkg/cmd/config/config.go b/pkg/cmd/config/config.go index 408975629..0fb02eb9d 100644 --- a/pkg/cmd/config/config.go +++ b/pkg/cmd/config/config.go @@ -1,9 +1,12 @@ package config import ( + "errors" "fmt" + "strings" "github.com/MakeNowJust/heredoc" + "github.com/cli/cli/internal/config" "github.com/cli/cli/pkg/cmdutil" "github.com/spf13/cobra" ) @@ -87,6 +90,14 @@ func NewCmdConfigSet(f *cmdutil.Factory) *cobra.Command { key, value := args[0], args[1] err = cfg.Set(hostname, key, value) if err != nil { + var invalidValue *config.InvalidValueError + if errors.As(err, &invalidValue) { + var values []string + for _, v := range invalidValue.ValidValues { + values = append(values, fmt.Sprintf("'%s'", v)) + } + return fmt.Errorf("failed to set %q to %q: valid values are %v", key, value, strings.Join(values, ", ")) + } return fmt.Errorf("failed to set %q to %q: %w", key, value, err) } From 7e9f1c7147ab823ed6d274283d8603deb4cabf69 Mon Sep 17 00:00:00 2001 From: XD-DENG Date: Mon, 19 Oct 2020 20:51:51 +0200 Subject: [PATCH 26/41] Use strings.Builder for more efficient string concatenation It minimizes memory copying. Reference: https://golang.org/pkg/strings/#Builder --- pkg/cmd/pr/create/create.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 09b2ba1f7..35040e9d4 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -501,11 +501,11 @@ func computeDefaults(baseRef, headRef string) (shared.Defaults, error) { } else { out.Title = utils.Humanize(headRef) - body := "" + var body strings.Builder for i := len(commits) - 1; i >= 0; i-- { - body += fmt.Sprintf("- %s\n", commits[i].Title) + fmt.Fprintf(&body, "- %s\n", commits[i].Title) } - out.Body = body + out.Body = body.String() } return out, nil From 9d6f5a8e75fc5ff684632c43f9d18b5ec453bcab Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 20 Oct 2020 08:51:42 +0000 Subject: [PATCH 27/41] Update spf13/cobra to v1.1.1 --- go.mod | 3 +- go.sum | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 166 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 7499bb2a7..fe0d39b7b 100644 --- a/go.mod +++ b/go.mod @@ -22,12 +22,11 @@ require ( github.com/rivo/uniseg v0.1.0 github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5 github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/text v0.3.3 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) diff --git a/go.sum b/go.sum index 89f276dd6..b3659cb76 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,21 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= @@ -19,9 +32,13 @@ github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkx github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -31,8 +48,8 @@ github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e h1:aq/1jlmtZo github.com/cli/shurcooL-graphql v0.0.0-20200707151639-0f7232a2bf7e/go.mod h1:it23pLwxmz6OyM6I5O0ATIXQS1S190Nas26L5Kahp4U= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= @@ -52,6 +69,7 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -61,26 +79,58 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v0.0.0-20200622220639-c1d9693c95a6/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= @@ -88,6 +138,9 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -104,13 +157,15 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -125,9 +180,18 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM= github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I= github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk= @@ -139,6 +203,7 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -146,6 +211,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -158,8 +224,11 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5 h1:CA6Mjshr+g5YHENwllpQNR0UaYO7VGKo6TzJLM64WJQ= @@ -167,18 +236,20 @@ github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5/go.mod h1:hAF0iL github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -188,48 +259,88 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -239,33 +350,73 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HX golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 0f8084d039bf7137cff27374df99c12887f63492 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Wed, 21 Oct 2020 10:46:09 +0200 Subject: [PATCH 28/41] Move functions from command/root to main --- cmd/gh/main.go | 29 ++++++++++++++++++++++++++++- command/root.go | 35 ----------------------------------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/cmd/gh/main.go b/cmd/gh/main.go index 242df7ed2..17b3a644e 100644 --- a/cmd/gh/main.go +++ b/cmd/gh/main.go @@ -208,7 +208,7 @@ func checkForUpdate(currentVersion string) (*update.ReleaseInfo, error) { return nil, nil } - client, err := command.BasicClient() + client, err := basicClient() if err != nil { return nil, err } @@ -217,3 +217,30 @@ func checkForUpdate(currentVersion string) (*update.ReleaseInfo, error) { stateFilePath := path.Join(config.ConfigDir(), "state.yml") return update.CheckForUpdate(client, stateFilePath, repo, currentVersion) } + +// BasicClient returns an API client for github.com only that borrows from but +// does not depend on user configuration +func basicClient() (*api.Client, error) { + var opts []api.ClientOption + if verbose := os.Getenv("DEBUG"); verbose != "" { + opts = append(opts, apiVerboseLog()) + } + opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", command.Version))) + + token := os.Getenv("GITHUB_TOKEN") + if token == "" { + if c, err := config.ParseDefaultConfig(); err == nil { + token, _ = c.Get(ghinstance.Default(), "oauth_token") + } + } + if token != "" { + opts = append(opts, api.AddHeader("Authorization", fmt.Sprintf("token %s", token))) + } + return api.NewClient(opts...), nil +} + +func apiVerboseLog() api.ClientOption { + logTraffic := strings.Contains(os.Getenv("DEBUG"), "api") + colorize := utils.IsTerminal(os.Stderr) + return api.VerboseLog(utils.NewColorable(os.Stderr), logTraffic, colorize) +} diff --git a/command/root.go b/command/root.go index 5e5260793..5660196e7 100644 --- a/command/root.go +++ b/command/root.go @@ -1,15 +1,7 @@ package command import ( - "fmt" - "os" "runtime/debug" - "strings" - - "github.com/cli/cli/api" - "github.com/cli/cli/internal/config" - "github.com/cli/cli/internal/ghinstance" - "github.com/cli/cli/utils" ) // Version is dynamically set by the toolchain or overridden by the Makefile. @@ -25,30 +17,3 @@ func init() { } } } - -// BasicClient returns an API client for github.com only that borrows from but -// does not depend on user configuration -func BasicClient() (*api.Client, error) { - var opts []api.ClientOption - if verbose := os.Getenv("DEBUG"); verbose != "" { - opts = append(opts, apiVerboseLog()) - } - opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", Version))) - - token := os.Getenv("GITHUB_TOKEN") - if token == "" { - if c, err := config.ParseDefaultConfig(); err == nil { - token, _ = c.Get(ghinstance.Default(), "oauth_token") - } - } - if token != "" { - opts = append(opts, api.AddHeader("Authorization", fmt.Sprintf("token %s", token))) - } - return api.NewClient(opts...), nil -} - -func apiVerboseLog() api.ClientOption { - logTraffic := strings.Contains(os.Getenv("DEBUG"), "api") - colorize := utils.IsTerminal(os.Stderr) - return api.VerboseLog(utils.NewColorable(os.Stderr), logTraffic, colorize) -} From 9df3bd9344c4979705c700ebc186efd30e5798a6 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Wed, 21 Oct 2020 11:06:05 +0200 Subject: [PATCH 29/41] Change command pkg to build pkg --- Makefile | 4 ++-- command/root.go => build/build.go | 6 +++--- cmd/gh/main.go | 20 +++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) rename command/root.go => build/build.go (71%) diff --git a/Makefile b/Makefile index 075a8865e..35837205e 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,8 @@ ifndef CGO_LDFLAGS export CGO_LDFLAGS := $(LDFLAGS) endif -GO_LDFLAGS := -X github.com/cli/cli/command.Version=$(GH_VERSION) $(GO_LDFLAGS) -GO_LDFLAGS := -X github.com/cli/cli/command.BuildDate=$(BUILD_DATE) $(GO_LDFLAGS) +GO_LDFLAGS := -X github.com/cli/cli/build.Version=$(GH_VERSION) $(GO_LDFLAGS) +GO_LDFLAGS := -X github.com/cli/cli/build.Date=$(BUILD_DATE) $(GO_LDFLAGS) ifdef GH_OAUTH_CLIENT_SECRET GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS) GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS) diff --git a/command/root.go b/build/build.go similarity index 71% rename from command/root.go rename to build/build.go index 5660196e7..8c3e78a7e 100644 --- a/command/root.go +++ b/build/build.go @@ -1,4 +1,4 @@ -package command +package build import ( "runtime/debug" @@ -7,8 +7,8 @@ import ( // Version is dynamically set by the toolchain or overridden by the Makefile. var Version = "DEV" -// BuildDate is dynamically set at build time in the Makefile. -var BuildDate = "" // YYYY-MM-DD +// Date is dynamically set at build time in the Makefile. +var Date = "" // YYYY-MM-DD func init() { if Version == "DEV" { diff --git a/cmd/gh/main.go b/cmd/gh/main.go index 17b3a644e..4d29709c4 100644 --- a/cmd/gh/main.go +++ b/cmd/gh/main.go @@ -12,7 +12,7 @@ import ( surveyCore "github.com/AlecAivazis/survey/v2/core" "github.com/cli/cli/api" - "github.com/cli/cli/command" + "github.com/cli/cli/build" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghinstance" "github.com/cli/cli/internal/run" @@ -29,10 +29,12 @@ import ( var updaterEnabled = "" func main() { - currentVersion := command.Version + buildDate := build.Date + buildVersion := build.Version + updateMessageChan := make(chan *update.ReleaseInfo) go func() { - rel, _ := checkForUpdate(currentVersion) + rel, _ := checkForUpdate(buildVersion) updateMessageChan <- rel }() @@ -42,7 +44,7 @@ func main() { ghinstance.OverrideDefault(hostFromEnv) } - cmdFactory := factory.New(command.Version) + cmdFactory := factory.New(buildVersion) stderr := cmdFactory.IOStreams.ErrOut if !cmdFactory.IOStreams.ColorEnabled() { surveyCore.DisableColor = true @@ -61,7 +63,7 @@ func main() { } } - rootCmd := root.NewCmdRoot(cmdFactory, command.Version, command.BuildDate) + rootCmd := root.NewCmdRoot(cmdFactory, buildVersion, buildDate) cfg, err := cmdFactory.Config() if err != nil { @@ -151,7 +153,7 @@ func main() { if newRelease != nil { msg := fmt.Sprintf("%s %s → %s\n%s", ansi.Color("A new release of gh is available:", "yellow"), - ansi.Color(currentVersion, "cyan"), + ansi.Color(buildVersion, "cyan"), ansi.Color(newRelease.Version, "cyan"), ansi.Color(newRelease.URL, "yellow")) @@ -208,7 +210,7 @@ func checkForUpdate(currentVersion string) (*update.ReleaseInfo, error) { return nil, nil } - client, err := basicClient() + client, err := basicClient(currentVersion) if err != nil { return nil, err } @@ -220,12 +222,12 @@ func checkForUpdate(currentVersion string) (*update.ReleaseInfo, error) { // BasicClient returns an API client for github.com only that borrows from but // does not depend on user configuration -func basicClient() (*api.Client, error) { +func basicClient(currentVersion string) (*api.Client, error) { var opts []api.ClientOption if verbose := os.Getenv("DEBUG"); verbose != "" { opts = append(opts, apiVerboseLog()) } - opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", command.Version))) + opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", currentVersion))) token := os.Getenv("GITHUB_TOKEN") if token == "" { From f0b534f92e268f10605c8a239126584619bf3ec1 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Wed, 21 Oct 2020 13:31:32 +0200 Subject: [PATCH 30/41] Move build to internal --- Makefile | 4 ++-- cmd/gh/main.go | 2 +- {build => internal/build}/build.go | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename {build => internal/build}/build.go (100%) diff --git a/Makefile b/Makefile index 35837205e..ad16069ee 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,8 @@ ifndef CGO_LDFLAGS export CGO_LDFLAGS := $(LDFLAGS) endif -GO_LDFLAGS := -X github.com/cli/cli/build.Version=$(GH_VERSION) $(GO_LDFLAGS) -GO_LDFLAGS := -X github.com/cli/cli/build.Date=$(BUILD_DATE) $(GO_LDFLAGS) +GO_LDFLAGS := -X github.com/cli/cli/internal/build.Version=$(GH_VERSION) $(GO_LDFLAGS) +GO_LDFLAGS := -X github.com/cli/cli/internal/build.Date=$(BUILD_DATE) $(GO_LDFLAGS) ifdef GH_OAUTH_CLIENT_SECRET GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS) GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS) diff --git a/cmd/gh/main.go b/cmd/gh/main.go index 4d29709c4..c393a627e 100644 --- a/cmd/gh/main.go +++ b/cmd/gh/main.go @@ -12,7 +12,7 @@ import ( surveyCore "github.com/AlecAivazis/survey/v2/core" "github.com/cli/cli/api" - "github.com/cli/cli/build" + "github.com/cli/cli/internal/build" "github.com/cli/cli/internal/config" "github.com/cli/cli/internal/ghinstance" "github.com/cli/cli/internal/run" diff --git a/build/build.go b/internal/build/build.go similarity index 100% rename from build/build.go rename to internal/build/build.go From d0bee19eafcfceb39e3fa2e88f3b845590f15791 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 21 Oct 2020 05:17:30 -0500 Subject: [PATCH 31/41] Query escape branch names to allow branch/#123 names --- pkg/cmd/pr/create/create.go | 7 ++++++- pkg/cmd/pr/create/create_test.go | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 09b2ba1f7..63e2fca2c 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "time" @@ -553,7 +554,11 @@ func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.Tr } func generateCompareURL(r ghrepo.Interface, base, head, title, body string, assignees, labels, projects []string, milestones []string) (string, error) { - u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", base, head) + // This is to fix issues with names like "branch/#123" + b := url.QueryEscape(base) + h := url.QueryEscape(head) + + u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", b, h) url, err := shared.WithPrAndIssueQueryParams(u, title, body, assignees, labels, projects, milestones) if err != nil { return "", err diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index ebedd9f4e..c3a54159f 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -636,6 +636,42 @@ func TestPRCreate_web(t *testing.T) { eq(t, browserCall[len(browserCall)-1], "https://github.com/OWNER/REPO/compare/master...feature?expand=1") } +func TestPRCreate_web_uncommon_branch_names(t *testing.T) { + http := initFakeHTTP() + defer http.Verify(t) + + http.StubRepoInfoResponse("OWNER", "REPO", "master") + http.StubRepoResponse("OWNER", "REPO") + http.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) + + cs, cmdTeardown := test.InitCmdStubber() + defer cmdTeardown() + + cs.Stub("") // git config --get-regexp (determineTrackingBranch) + cs.Stub("") // git show-ref --verify (determineTrackingBranch) + cs.Stub("") // git status + cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log + cs.Stub("") // git push + cs.Stub("") // browser + + ask, cleanupAsk := prompt.InitAskStubber() + defer cleanupAsk() + ask.StubOne(0) + + output, err := runCommand(http, nil, "feature/#123", true, `--web`) + require.NoError(t, err) + + eq(t, output.String(), "") + eq(t, output.Stderr(), "Opening github.com/OWNER/REPO/compare/master...feature/#123 in your browser.\n") + + eq(t, len(cs.Calls), 6) + eq(t, strings.Join(cs.Calls[4].Args, " "), "git push --set-upstream origin HEAD:feature/#123") + browserCall := cs.Calls[5].Args + eq(t, browserCall[len(browserCall)-1], "https://github.com/OWNER/REPO/compare/master...feature%2F%23123?expand=1") +} + func Test_determineTrackingBranch_empty(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() From d5cb5574d01671bc3bae24cbfc7fa04f29102e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 21 Oct 2020 11:55:20 +0000 Subject: [PATCH 32/41] :nail_care: simplify `generateCompareURL` and test --- pkg/cmd/pr/create/create.go | 6 +- pkg/cmd/pr/create/create_test.go | 100 ++++++++++++++++++++----------- 2 files changed, 65 insertions(+), 41 deletions(-) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 63e2fca2c..8ad123226 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -554,11 +554,7 @@ func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.Tr } func generateCompareURL(r ghrepo.Interface, base, head, title, body string, assignees, labels, projects []string, milestones []string) (string, error) { - // This is to fix issues with names like "branch/#123" - b := url.QueryEscape(base) - h := url.QueryEscape(head) - - u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", b, h) + u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", url.QueryEscape(base), url.QueryEscape(head)) url, err := shared.WithPrAndIssueQueryParams(u, title, body, assignees, labels, projects, milestones) if err != nil { return "", err diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index c3a54159f..456da84a9 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -636,42 +636,6 @@ func TestPRCreate_web(t *testing.T) { eq(t, browserCall[len(browserCall)-1], "https://github.com/OWNER/REPO/compare/master...feature?expand=1") } -func TestPRCreate_web_uncommon_branch_names(t *testing.T) { - http := initFakeHTTP() - defer http.Verify(t) - - http.StubRepoInfoResponse("OWNER", "REPO", "master") - http.StubRepoResponse("OWNER", "REPO") - http.Register( - httpmock.GraphQL(`query UserCurrent\b`), - httpmock.StringResponse(`{"data": {"viewer": {"login": "OWNER"} } }`)) - - cs, cmdTeardown := test.InitCmdStubber() - defer cmdTeardown() - - cs.Stub("") // git config --get-regexp (determineTrackingBranch) - cs.Stub("") // git show-ref --verify (determineTrackingBranch) - cs.Stub("") // git status - cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log - cs.Stub("") // git push - cs.Stub("") // browser - - ask, cleanupAsk := prompt.InitAskStubber() - defer cleanupAsk() - ask.StubOne(0) - - output, err := runCommand(http, nil, "feature/#123", true, `--web`) - require.NoError(t, err) - - eq(t, output.String(), "") - eq(t, output.Stderr(), "Opening github.com/OWNER/REPO/compare/master...feature/#123 in your browser.\n") - - eq(t, len(cs.Calls), 6) - eq(t, strings.Join(cs.Calls[4].Args, " "), "git push --set-upstream origin HEAD:feature/#123") - browserCall := cs.Calls[5].Args - eq(t, browserCall[len(browserCall)-1], "https://github.com/OWNER/REPO/compare/master...feature%2F%23123?expand=1") -} - func Test_determineTrackingBranch_empty(t *testing.T) { cs, cmdTeardown := test.InitCmdStubber() defer cmdTeardown() @@ -766,3 +730,67 @@ deadb00f refs/remotes/origin/feature`) // git show-ref --verify (ShowRefs) eq(t, cs.Calls[1].Args, []string{"git", "show-ref", "--verify", "--", "HEAD", "refs/remotes/origin/great-feat", "refs/remotes/origin/feature"}) } + +func Test_generateCompareURL(t *testing.T) { + type args struct { + r ghrepo.Interface + base string + head string + title string + body string + assignees []string + labels []string + projects []string + milestones []string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "basic", + args: args{ + r: ghrepo.New("OWNER", "REPO"), + base: "main", + head: "feature", + }, + want: "https://github.com/OWNER/REPO/compare/main...feature?expand=1", + wantErr: false, + }, + { + name: "with labels", + args: args{ + r: ghrepo.New("OWNER", "REPO"), + base: "a", + head: "b", + labels: []string{"one", "two three"}, + }, + want: "https://github.com/OWNER/REPO/compare/a...b?expand=1&labels=one%2Ctwo+three", + wantErr: false, + }, + { + name: "complex branch names", + args: args{ + r: ghrepo.New("OWNER", "REPO"), + base: "main/trunk", + head: "owner:feature", + }, + want: "https://github.com/OWNER/REPO/compare/main%2Ftrunk...owner%3Afeature?expand=1", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := generateCompareURL(tt.args.r, tt.args.base, tt.args.head, tt.args.title, tt.args.body, tt.args.assignees, tt.args.labels, tt.args.projects, tt.args.milestones) + if (err != nil) != tt.wantErr { + t.Errorf("generateCompareURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("generateCompareURL() = %v, want %v", got, tt.want) + } + }) + } +} From f31e49fdff381c883d62ee7b0d8e0d09dfcc77f7 Mon Sep 17 00:00:00 2001 From: Shrihan Kumar Padhy <54971391+shrihanDev@users.noreply.github.com> Date: Wed, 21 Oct 2020 20:26:25 +0530 Subject: [PATCH 33/41] Add a new config validation key called `prompt` (#2246) --- internal/config/config_type.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/config/config_type.go b/internal/config/config_type.go index badf7ee00..a28521f0e 100644 --- a/internal/config/config_type.go +++ b/internal/config/config_type.go @@ -18,6 +18,7 @@ const ( var configValues = map[string][]string{ "git_protocol": {"ssh", "https"}, + "prompt": {"enabled", "disabled"}, } // This interface describes interacting with some persistent configuration for gh. From 7512d9131fa9a22388f8abcdd9ae13692969d7da Mon Sep 17 00:00:00 2001 From: Ishida Yuya Date: Thu, 22 Oct 2020 01:22:06 +0900 Subject: [PATCH 34/41] Get open and closed milestones when milestones are filtered by title (#2209) --- api/queries_issue.go | 2 +- api/queries_repo.go | 25 +++++++++++++++----- api/queries_repo_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/api/queries_issue.go b/api/queries_issue.go index 42fc9e196..690180feb 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -257,7 +257,7 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str return nil, err } } else { - milestone, err = MilestoneByTitle(client, repo, milestoneString) + milestone, err = MilestoneByTitle(client, repo, "all", milestoneString) if err != nil { return nil, err } diff --git a/api/queries_repo.go b/api/queries_repo.go index 402afc783..54f88a7e1 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -536,7 +536,7 @@ func RepoMetadata(client *Client, repo ghrepo.Interface, input RepoMetadataInput if input.Milestones { count++ go func() { - milestones, err := RepoMilestones(client, repo) + milestones, err := RepoMilestones(client, repo, "open") if err != nil { err = fmt.Errorf("error fetching milestones: %w", err) } @@ -797,8 +797,8 @@ type RepoMilestone struct { Title string } -// RepoMilestones fetches all open milestones in a repository -func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, error) { +// RepoMilestones fetches milestones in a repository +func RepoMilestones(client *Client, repo ghrepo.Interface, state string) ([]RepoMilestone, error) { type responseData struct { Repository struct { Milestones struct { @@ -807,13 +807,26 @@ func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, err HasNextPage bool EndCursor string } - } `graphql:"milestones(states: [OPEN], first: 100, after: $endCursor)"` + } `graphql:"milestones(states: $states, first: 100, after: $endCursor)"` } `graphql:"repository(owner: $owner, name: $name)"` } + var states []githubv4.MilestoneState + switch state { + case "open": + states = []githubv4.MilestoneState{"OPEN"} + case "closed": + states = []githubv4.MilestoneState{"CLOSED"} + case "all": + states = []githubv4.MilestoneState{"OPEN", "CLOSED"} + default: + return nil, fmt.Errorf("invalid state: %s", state) + } + variables := map[string]interface{}{ "owner": githubv4.String(repo.RepoOwner()), "name": githubv4.String(repo.RepoName()), + "states": states, "endCursor": (*githubv4.String)(nil), } @@ -837,8 +850,8 @@ func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, err return milestones, nil } -func MilestoneByTitle(client *Client, repo ghrepo.Interface, title string) (*RepoMilestone, error) { - milestones, err := RepoMilestones(client, repo) +func MilestoneByTitle(client *Client, repo ghrepo.Interface, state, title string) (*RepoMilestone, error) { + milestones, err := RepoMilestones(client, repo, state) if err != nil { return nil, err } diff --git a/api/queries_repo_test.go b/api/queries_repo_test.go index ad0e36864..a90f6bea5 100644 --- a/api/queries_repo_test.go +++ b/api/queries_repo_test.go @@ -1,6 +1,9 @@ package api import ( + "io" + "net/http" + "strings" "testing" "github.com/cli/cli/internal/ghrepo" @@ -233,3 +236,51 @@ func sliceEqual(a, b []string) bool { return true } + +func Test_RepoMilestones(t *testing.T) { + tests := []struct { + state string + want string + wantErr bool + }{ + { + state: "open", + want: `"states":["OPEN"]`, + }, + { + state: "closed", + want: `"states":["CLOSED"]`, + }, + { + state: "all", + want: `"states":["OPEN","CLOSED"]`, + }, + { + state: "invalid state", + wantErr: true, + }, + } + for _, tt := range tests { + var query string + reg := &httpmock.Registry{} + reg.Register(httpmock.MatchAny, func(req *http.Request) (*http.Response, error) { + buf := new(strings.Builder) + _, err := io.Copy(buf, req.Body) + if err != nil { + return nil, err + } + query = buf.String() + return httpmock.StringResponse("{}")(req) + }) + client := NewClient(ReplaceTripper(reg)) + + _, err := RepoMilestones(client, ghrepo.New("OWNER", "REPO"), tt.state) + if (err != nil) != tt.wantErr { + t.Errorf("RepoMilestones() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !strings.Contains(query, tt.want) { + t.Errorf("query does not contain %v", tt.want) + } + } +} From 3ecb9de1a7eb9d652e68bbdda4b94f399e43b7ab Mon Sep 17 00:00:00 2001 From: wilso199 Date: Thu, 1 Oct 2020 14:49:00 -0400 Subject: [PATCH 35/41] Adding a hostname flag option for use with gh api --- pkg/cmd/api/api.go | 14 ++++++++++- pkg/cmd/api/api_test.go | 27 +++++++++++++++++++++ pkg/cmd/auth/login/login.go | 15 ++---------- utils/utils.go | 16 +++++++++++++ utils/utils_test.go | 47 +++++++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 14 deletions(-) diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go index 9ec4de435..67883d2b8 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -21,12 +21,14 @@ import ( "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" "github.com/cli/cli/pkg/jsoncolor" + "github.com/cli/cli/utils" "github.com/spf13/cobra" ) type ApiOptions struct { IO *iostreams.IOStreams + Hostname string RequestMethod string RequestMethodPassed bool RequestPath string @@ -101,7 +103,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the } } ' - + $ gh api graphql --paginate -f query=' query($endCursor: String) { viewer { @@ -128,6 +130,12 @@ original query accepts an '$endCursor: String' variable and that it fetches the opts.RequestPath = args[0] opts.RequestMethodPassed = c.Flags().Changed("method") + if c.Flags().Changed("hostname") { + if err := utils.HostnameValidator(opts.Hostname); err != nil { + return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)} + } + } + if opts.Paginate && !strings.EqualFold(opts.RequestMethod, "GET") && opts.RequestPath != "graphql" { return &cmdutil.FlagError{Err: errors.New(`the '--paginate' option is not supported for non-GET requests`)} } @@ -142,6 +150,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the }, } + cmd.Flags().StringVar(&opts.Hostname, "hostname", "", "The hostname of the GitHub instance for the request") cmd.Flags().StringVarP(&opts.RequestMethod, "method", "X", "GET", "The HTTP method for the request") cmd.Flags().StringArrayVarP(&opts.MagicFields, "field", "F", nil, "Add a parameter of inferred type") cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter") @@ -206,6 +215,9 @@ func apiRun(opts *ApiOptions) error { } host := ghinstance.OverridableDefault() + if opts.Hostname != "" { + host = opts.Hostname + } hasNextPage := true for hasNextPage { diff --git a/pkg/cmd/api/api_test.go b/pkg/cmd/api/api_test.go index a810fa7fc..6cd693ce1 100644 --- a/pkg/cmd/api/api_test.go +++ b/pkg/cmd/api/api_test.go @@ -31,6 +31,7 @@ func Test_NewCmdApi(t *testing.T) { name: "no flags", cli: "graphql", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "graphql", @@ -48,6 +49,7 @@ func Test_NewCmdApi(t *testing.T) { name: "override method", cli: "repos/octocat/Spoon-Knife -XDELETE", wants: ApiOptions{ + Hostname: "", RequestMethod: "DELETE", RequestMethodPassed: true, RequestPath: "repos/octocat/Spoon-Knife", @@ -65,6 +67,7 @@ func Test_NewCmdApi(t *testing.T) { name: "with fields", cli: "graphql -f query=QUERY -F body=@file.txt", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "graphql", @@ -82,6 +85,7 @@ func Test_NewCmdApi(t *testing.T) { name: "with headers", cli: "user -H 'accept: text/plain' -i", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "user", @@ -99,6 +103,7 @@ func Test_NewCmdApi(t *testing.T) { name: "with pagination", cli: "repos/OWNER/REPO/issues --paginate", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "repos/OWNER/REPO/issues", @@ -116,6 +121,7 @@ func Test_NewCmdApi(t *testing.T) { name: "with silenced output", cli: "repos/OWNER/REPO/issues --silent", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "repos/OWNER/REPO/issues", @@ -138,6 +144,7 @@ func Test_NewCmdApi(t *testing.T) { name: "GraphQL pagination", cli: "-XPOST graphql --paginate", wants: ApiOptions{ + Hostname: "", RequestMethod: "POST", RequestMethodPassed: true, RequestPath: "graphql", @@ -160,6 +167,7 @@ func Test_NewCmdApi(t *testing.T) { name: "with request body from file", cli: "user --input myfile", wants: ApiOptions{ + Hostname: "", RequestMethod: "GET", RequestMethodPassed: false, RequestPath: "user", @@ -178,10 +186,29 @@ func Test_NewCmdApi(t *testing.T) { cli: "", wantsErr: true, }, + { + name: "with hostname", + cli: "graphql --hostname tom.petty", + wants: ApiOptions{ + Hostname: "tom.petty", + RequestMethod: "GET", + RequestMethodPassed: false, + RequestPath: "graphql", + RequestInputFile: "", + RawFields: []string(nil), + MagicFields: []string(nil), + RequestHeaders: []string(nil), + ShowResponseHeaders: false, + Paginate: false, + Silent: false, + }, + wantsErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cmd := NewCmdApi(f, func(o *ApiOptions) error { + assert.Equal(t, tt.wants.Hostname, o.Hostname) assert.Equal(t, tt.wants.RequestMethod, o.RequestMethod) assert.Equal(t, tt.wants.RequestMethodPassed, o.RequestMethodPassed) assert.Equal(t, tt.wants.RequestPath, o.RequestPath) diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index 7ded3056b..c3c310cdb 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -88,7 +88,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm } if cmd.Flags().Changed("hostname") { - if err := hostnameValidator(opts.Hostname); err != nil { + if err := utils.HostnameValidator(opts.Hostname); err != nil { return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)} } } @@ -166,7 +166,7 @@ func loginRun(opts *LoginOptions) error { if isEnterprise { err := prompt.SurveyAskOne(&survey.Input{ Message: "GHE hostname:", - }, &hostname, survey.WithValidator(hostnameValidator)) + }, &hostname, survey.WithValidator(utils.HostnameValidator)) if err != nil { return fmt.Errorf("could not prompt: %w", err) } @@ -307,17 +307,6 @@ func loginRun(opts *LoginOptions) error { return nil } -func hostnameValidator(v interface{}) error { - val := v.(string) - if len(strings.TrimSpace(val)) < 1 { - return errors.New("a value is required") - } - if strings.ContainsRune(val, '/') || strings.ContainsRune(val, ':') { - return errors.New("invalid hostname") - } - return nil -} - func getAccessTokenTip(hostname string) string { ghHostname := hostname if ghHostname == "" { diff --git a/utils/utils.go b/utils/utils.go index 69e48c150..155a04fca 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "io" "net/url" @@ -28,6 +29,21 @@ func OpenInBrowser(url string) error { return err } +func HostnameValidator(v interface{}) error { + hostname, valid := v.(string) + if !valid { + return errors.New("hostname is not a string") + } + + if len(strings.TrimSpace(hostname)) < 1 { + return errors.New("a value is required") + } + if strings.ContainsRune(hostname, '/') || strings.ContainsRune(hostname, ':') { + return errors.New("invalid hostname") + } + return nil +} + func Pluralize(num int, thing string) string { if num == 1 { return fmt.Sprintf("%d %s", num, thing) diff --git a/utils/utils_test.go b/utils/utils_test.go index 0891c2a39..9ed8d2afa 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -3,6 +3,8 @@ package utils import ( "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestFuzzyAgo(t *testing.T) { @@ -36,3 +38,48 @@ func TestFuzzyAgo(t *testing.T) { } } } + +func TestHostnameValidator(t *testing.T) { + tests := []struct { + name string + input interface{} + wantsErr bool + }{ + { + name: "valid hostname", + input: "internal.instance", + wantsErr: false, + }, + { + name: "hostname with slashes", + input: "//internal.instance", + wantsErr: true, + }, + { + name: "empty hostname", + input: " ", + wantsErr: true, + }, + { + name: "hostname with colon", + input: "internal.instance:2205", + wantsErr: true, + }, + { + name: "non-string hostname", + input: 62, + wantsErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := HostnameValidator(tt.input) + if tt.wantsErr { + assert.Error(t, err) + return + } + assert.Equal(t, nil, err) + }) + } +} From 460d55f7233650e2801cb65159786678a539d1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 21 Oct 2020 16:30:45 +0000 Subject: [PATCH 36/41] Move HostnameValidator to ghinstance --- internal/ghinstance/host.go | 16 +++++++++++ internal/ghinstance/host_test.go | 46 +++++++++++++++++++++++++++++++ pkg/cmd/api/api.go | 3 +- pkg/cmd/auth/login/login.go | 4 +-- utils/utils.go | 16 ----------- utils/utils_test.go | 47 -------------------------------- 6 files changed, 65 insertions(+), 67 deletions(-) diff --git a/internal/ghinstance/host.go b/internal/ghinstance/host.go index 642dd0846..76639ed26 100644 --- a/internal/ghinstance/host.go +++ b/internal/ghinstance/host.go @@ -1,6 +1,7 @@ package ghinstance import ( + "errors" "fmt" "strings" ) @@ -42,6 +43,21 @@ func NormalizeHostname(h string) string { return hostname } +func HostnameValidator(v interface{}) error { + hostname, valid := v.(string) + if !valid { + return errors.New("hostname is not a string") + } + + if len(strings.TrimSpace(hostname)) < 1 { + return errors.New("a value is required") + } + if strings.ContainsRune(hostname, '/') || strings.ContainsRune(hostname, ':') { + return errors.New("invalid hostname") + } + return nil +} + func GraphQLEndpoint(hostname string) string { if IsEnterprise(hostname) { return fmt.Sprintf("https://%s/api/graphql", hostname) diff --git a/internal/ghinstance/host_test.go b/internal/ghinstance/host_test.go index 10a40a432..787569c68 100644 --- a/internal/ghinstance/host_test.go +++ b/internal/ghinstance/host_test.go @@ -2,6 +2,8 @@ package ghinstance import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestOverridableDefault(t *testing.T) { @@ -97,6 +99,50 @@ func TestNormalizeHostname(t *testing.T) { } } +func TestHostnameValidator(t *testing.T) { + tests := []struct { + name string + input interface{} + wantsErr bool + }{ + { + name: "valid hostname", + input: "internal.instance", + wantsErr: false, + }, + { + name: "hostname with slashes", + input: "//internal.instance", + wantsErr: true, + }, + { + name: "empty hostname", + input: " ", + wantsErr: true, + }, + { + name: "hostname with colon", + input: "internal.instance:2205", + wantsErr: true, + }, + { + name: "non-string hostname", + input: 62, + wantsErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := HostnameValidator(tt.input) + if tt.wantsErr { + assert.Error(t, err) + return + } + assert.Equal(t, nil, err) + }) + } +} func TestGraphQLEndpoint(t *testing.T) { tests := []struct { host string diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go index 67883d2b8..8af33e78e 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -21,7 +21,6 @@ import ( "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" "github.com/cli/cli/pkg/jsoncolor" - "github.com/cli/cli/utils" "github.com/spf13/cobra" ) @@ -131,7 +130,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the opts.RequestMethodPassed = c.Flags().Changed("method") if c.Flags().Changed("hostname") { - if err := utils.HostnameValidator(opts.Hostname); err != nil { + if err := ghinstance.HostnameValidator(opts.Hostname); err != nil { return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)} } } diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index c3c310cdb..929d6b61d 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -88,7 +88,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm } if cmd.Flags().Changed("hostname") { - if err := utils.HostnameValidator(opts.Hostname); err != nil { + if err := ghinstance.HostnameValidator(opts.Hostname); err != nil { return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)} } } @@ -166,7 +166,7 @@ func loginRun(opts *LoginOptions) error { if isEnterprise { err := prompt.SurveyAskOne(&survey.Input{ Message: "GHE hostname:", - }, &hostname, survey.WithValidator(utils.HostnameValidator)) + }, &hostname, survey.WithValidator(ghinstance.HostnameValidator)) if err != nil { return fmt.Errorf("could not prompt: %w", err) } diff --git a/utils/utils.go b/utils/utils.go index 155a04fca..69e48c150 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,7 +1,6 @@ package utils import ( - "errors" "fmt" "io" "net/url" @@ -29,21 +28,6 @@ func OpenInBrowser(url string) error { return err } -func HostnameValidator(v interface{}) error { - hostname, valid := v.(string) - if !valid { - return errors.New("hostname is not a string") - } - - if len(strings.TrimSpace(hostname)) < 1 { - return errors.New("a value is required") - } - if strings.ContainsRune(hostname, '/') || strings.ContainsRune(hostname, ':') { - return errors.New("invalid hostname") - } - return nil -} - func Pluralize(num int, thing string) string { if num == 1 { return fmt.Sprintf("%d %s", num, thing) diff --git a/utils/utils_test.go b/utils/utils_test.go index 9ed8d2afa..0891c2a39 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -3,8 +3,6 @@ package utils import ( "testing" "time" - - "github.com/stretchr/testify/assert" ) func TestFuzzyAgo(t *testing.T) { @@ -38,48 +36,3 @@ func TestFuzzyAgo(t *testing.T) { } } } - -func TestHostnameValidator(t *testing.T) { - tests := []struct { - name string - input interface{} - wantsErr bool - }{ - { - name: "valid hostname", - input: "internal.instance", - wantsErr: false, - }, - { - name: "hostname with slashes", - input: "//internal.instance", - wantsErr: true, - }, - { - name: "empty hostname", - input: " ", - wantsErr: true, - }, - { - name: "hostname with colon", - input: "internal.instance:2205", - wantsErr: true, - }, - { - name: "non-string hostname", - input: 62, - wantsErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := HostnameValidator(tt.input) - if tt.wantsErr { - assert.Error(t, err) - return - } - assert.Equal(t, nil, err) - }) - } -} From 228191bf855b417d5b69192795e5305e7598f11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 21 Oct 2020 16:43:48 +0000 Subject: [PATCH 37/41] Document GH_HOST for api command --- pkg/cmd/api/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go index 8af33e78e..f73add092 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -122,6 +122,8 @@ original query accepts an '$endCursor: String' variable and that it fetches the GITHUB_TOKEN: an authentication token for github.com API requests. GITHUB_ENTERPRISE_TOKEN: an authentication token for API requests to GitHub Enterprise. + + GH_HOST: make the request to a GitHub host other than github.com. `), }, Args: cobra.ExactArgs(1), From fbff1fcfd213fcd9e02558afd924611884b34bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 21 Oct 2020 16:45:06 +0000 Subject: [PATCH 38/41] Clarify the `--hostname` flag --- pkg/cmd/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go index f73add092..4d64a260b 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -151,7 +151,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the }, } - cmd.Flags().StringVar(&opts.Hostname, "hostname", "", "The hostname of the GitHub instance for the request") + cmd.Flags().StringVar(&opts.Hostname, "hostname", "", "The GitHub hostname for the request (default \"github.com\")") cmd.Flags().StringVarP(&opts.RequestMethod, "method", "X", "GET", "The HTTP method for the request") cmd.Flags().StringArrayVarP(&opts.MagicFields, "field", "F", nil, "Add a parameter of inferred type") cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter") From 101bb90ab56848bdeb4f3ccd83c3a9b8ba949c0c Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Thu, 22 Oct 2020 17:51:16 +0200 Subject: [PATCH 39/41] Add host back into repo flag description --- pkg/cmdutil/repo_override.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmdutil/repo_override.go b/pkg/cmdutil/repo_override.go index aede57ea6..8b3d36489 100644 --- a/pkg/cmdutil/repo_override.go +++ b/pkg/cmdutil/repo_override.go @@ -8,7 +8,7 @@ import ( ) func EnableRepoOverride(cmd *cobra.Command, f *Factory) { - cmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `OWNER/REPO` format") + cmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `[HOST/]OWNER/REPO` format") cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { repoOverride, _ := cmd.Flags().GetString("repo") From d5403fa5f5bc4408f86ece9cf191de12f8054c27 Mon Sep 17 00:00:00 2001 From: zamasu Date: Fri, 23 Oct 2020 09:57:47 +0900 Subject: [PATCH 40/41] Change feedback repo to github.com/cli/cli instead of cli/cli --- pkg/cmd/root/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 7a82f0cbf..d1b5d9a9d 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -39,7 +39,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command { `), Annotations: map[string]string{ "help:feedback": heredoc.Doc(` - Open an issue using 'gh issue create -R cli/cli' + Open an issue using 'gh issue create -R github.com/cli/cli' `), "help:environment": heredoc.Doc(` See 'gh help environment' for the list of supported environment variables. From 3fcd791c9462dd774d15c1434f858414d59d3106 Mon Sep 17 00:00:00 2001 From: Sam Coe Date: Mon, 26 Oct 2020 09:11:10 +0300 Subject: [PATCH 41/41] Update LDFLAGS in goreleaser --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index c34aff15a..f04e7f7f2 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -15,7 +15,7 @@ builds: binary: bin/gh main: ./cmd/gh ldflags: - - -s -w -X github.com/cli/cli/command.Version={{.Version}} -X github.com/cli/cli/command.BuildDate={{time "2006-01-02"}} + - -s -w -X github.com/cli/cli/internal/build.Version={{.Version}} -X github.com/cli/cli/internal/build.Date={{time "2006-01-02"}} - -X main.updaterEnabled=cli/cli id: macos goos: [darwin]