diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a7889ed07..c15d1e8b0 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,11 +11,11 @@ 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 labelled [`help wanted`][hw] or [`good first issue`][gfi]. 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. @@ -52,6 +46,18 @@ 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 +[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 +[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/.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] diff --git a/Makefile b/Makefile index 075a8865e..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/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/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/README.md b/README.md index 9c3e25bb2..e327f5490 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]. There you will find instructions for sharing your feedback, building the tool locally, and submitting pull requests to the project. @@ -26,25 +20,25 @@ 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][]. +`gh` is available via [Homebrew][], [MacPorts][], and as 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 from the [releases page][]. + +For more information and distro-specific instructions, see the [Linux installation docs](./docs/install_linux.md). ### Windows @@ -67,10 +61,9 @@ scoop update gh #### Chocolatey -|Install:|Upgrade:| -|---|---| -|`choco install gh`|`choco upgrade gh`| - +| Install: | Upgrade: | +| ------------------ | ------------------ | +| `choco install gh` | `choco upgrade gh` | #### Signed MSI @@ -92,13 +85,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 diff --git a/api/queries_issue.go b/api/queries_issue.go index d607901a3..690180feb 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -251,13 +251,13 @@ 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 } } else { - milestone, err = MilestoneByTitle(client, repo, milestoneString) + milestone, err = MilestoneByTitle(client, repo, "all", milestoneString) if err != nil { return nil, err } @@ -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..83dee55b7 100644 --- a/api/queries_issue_test.go +++ b/api/queries_issue_test.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "testing" + "github.com/stretchr/testify/assert" + "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/pkg/httpmock" ) @@ -68,3 +70,76 @@ 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") + res, err := IssueList(client, repo, "", nil, "", 0, "", "", "") + if err != nil { + t.Fatalf("IssueList() error = %v", err) + } + + 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])) +} 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..54f88a7e1 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 } } }` @@ -529,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) } @@ -657,7 +664,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 +686,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 +709,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 +731,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 +754,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 +776,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 @@ -787,9 +797,9 @@ type RepoMilestone struct { Title string } -// RepoMilestones fetches all open milestones in a repository -func RepoMilestones(client *Client, repo ghrepo.Interface) ([]RepoMilestone, error) { - var query struct { +// RepoMilestones fetches milestones in a repository +func RepoMilestones(client *Client, repo ghrepo.Interface, state string) ([]RepoMilestone, error) { + type responseData struct { Repository struct { Milestones struct { Nodes []RepoMilestone @@ -797,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), } @@ -811,6 +834,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 @@ -826,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 } @@ -840,7 +864,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)"` 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) + } + } +} diff --git a/cmd/gh/main.go b/cmd/gh/main.go index 242df7ed2..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/command" + "github.com/cli/cli/internal/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 := command.BasicClient() + client, err := basicClient(currentVersion) if err != nil { return nil, err } @@ -217,3 +219,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(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", currentVersion))) + + 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 deleted file mode 100644 index 5e5260793..000000000 --- a/command/root.go +++ /dev/null @@ -1,54 +0,0 @@ -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. -var Version = "DEV" - -// BuildDate is dynamically set at build time in the Makefile. -var BuildDate = "" // YYYY-MM-DD - -func init() { - if Version == "DEV" { - if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" { - Version = info.Main.Version - } - } -} - -// 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) -} diff --git a/docs/triage.md b/docs/triage.md index d8aa83343..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 + - add `help wanted` label + - 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) 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= diff --git a/internal/build/build.go b/internal/build/build.go new file mode 100644 index 000000000..8c3e78a7e --- /dev/null +++ b/internal/build/build.go @@ -0,0 +1,19 @@ +package build + +import ( + "runtime/debug" +) + +// Version is dynamically set by the toolchain or overridden by the Makefile. +var Version = "DEV" + +// Date is dynamically set at build time in the Makefile. +var Date = "" // YYYY-MM-DD + +func init() { + if Version == "DEV" { + if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" { + Version = info.Main.Version + } + } +} diff --git a/internal/config/config_type.go b/internal/config/config_type.go index 0fd85d7f0..a28521f0e 100644 --- a/internal/config/config_type.go +++ b/internal/config/config_type.go @@ -16,6 +16,11 @@ const ( PromptsEnabled = "enabled" ) +var configValues = map[string][]string{ + "git_protocol": {"ssh", "https"}, + "prompt": {"enabled", "disabled"}, +} + // This interface describes interacting with some persistent configuration for gh. type Config interface { Get(string, string) (string, error) @@ -271,7 +276,33 @@ func (c *fileConfig) GetWithSource(hostname, key string) (string, string, error) return value, defaultSource, nil } +type InvalidValueError struct { + ValidValues []string +} + +func (e InvalidValueError) Error() string { + return "invalid value" +} + +func validateConfigEntry(key, value string) error { + validValues, found := configValues[key] + if !found { + return nil + } + + for _, v := range validValues { + if v == value { + return nil + } + } + + return &InvalidValueError{ValidValues: 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..46b9016f4 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") } 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") + + 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) +} 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 9ec4de435..4d64a260b 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -27,6 +27,7 @@ import ( type ApiOptions struct { IO *iostreams.IOStreams + Hostname string RequestMethod string RequestMethodPassed bool RequestPath string @@ -101,7 +102,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the } } ' - + $ gh api graphql --paginate -f query=' query($endCursor: String) { viewer { @@ -121,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), @@ -128,6 +131,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 := ghinstance.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 +151,7 @@ original query accepts an '$endCursor: String' variable and that it fetches the }, } + 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") @@ -206,6 +216,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 bd02ed9e4..929d6b61d 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 @@ -84,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 := ghinstance.HostnameValidator(opts.Hostname); err != nil { return &cmdutil.FlagError{Err: fmt.Errorf("error parsing --hostname: %w", err)} } } @@ -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") @@ -161,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(ghinstance.HostnameValidator)) if err != nil { return fmt.Errorf("could not prompt: %w", err) } @@ -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) } @@ -302,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/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) }) } } 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 { diff --git a/pkg/cmd/config/config.go b/pkg/cmd/config/config.go index 43c608f5a..f9cbbc9d1 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" ) @@ -111,6 +114,14 @@ func NewCmdConfigSet(f *cmdutil.Factory) *cobra.Command { } 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) } diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 09b2ba1f7..670491ac7 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" @@ -501,11 +502,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 @@ -553,7 +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) { - u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", base, head) + 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 ebedd9f4e..456da84a9 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -730,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) + } + }) + } +} diff --git a/pkg/cmd/pr/merge/merge.go b/pkg/cmd/pr/merge/merge.go index 86f6a81a5..e22b6c13d 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 diff --git a/pkg/cmd/pr/merge/merge_test.go b/pkg/cmd/pr/merge/merge_test.go index c29b794ae..5b272bff1 100644 --- a/pkg/cmd/pr/merge/merge_test.go +++ b/pkg/cmd/pr/merge/merge_test.go @@ -198,6 +198,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 @@ -244,6 +246,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 @@ -287,6 +291,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) @@ -322,6 +328,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` @@ -356,6 +364,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 @@ -390,6 +401,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 @@ -436,6 +449,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 @@ -481,6 +496,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 @@ -507,6 +524,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 @@ -570,6 +589,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) 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 diff --git a/pkg/cmd/repo/clone/clone.go b/pkg/cmd/repo/clone/clone.go index 239fbc21c..6dc225538 100644 --- a/pkg/cmd/repo/clone/clone.go +++ b/pkg/cmd/repo/clone/clone.go @@ -82,54 +82,68 @@ 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") - if err != nil { - return err - } - cloneURL = ghrepo.FormatRemoteURL(repo, protocol) - } - - var repo ghrepo.Interface - 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) + protocol, err = cfg.Get(repo.RepoHost(), "git_protocol") if err != nil { return err } } - cloneDir, err := git.RunClone(cloneURL, opts.GitArgs) + // Load the repo from the API to get the username/repo name in its + // canonical capitalization + 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 parentRepo != nil { - protocol, err := cfg.Get(parentRepo.RepoHost(), "git_protocol") + // If the repo is a fork, add the parent as an upstream + 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 54aa2f1cb..f696cd354 100644 --- a/pkg/cmd/repo/clone/clone_test.go +++ b/pkg/cmd/repo/clone/clone_test.go @@ -77,22 +77,30 @@ 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: "Owner/Repo", + want: "git clone https://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 RepositoryFindParent\b`), + httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` { "data": { "repository": { - "parent": null + "name": "REPO", + "owner": { + "login": "OWNER" + } } } } `)) @@ -120,15 +128,21 @@ func Test_RepoClone(t *testing.T) { func Test_RepoClone_hasParent(t *testing.T) { reg := &httpmock.Registry{} reg.Register( - httpmock.GraphQL(`query RepositoryFindParent\b`), + httpmock.GraphQL(`query RepositoryInfo\b`), httpmock.StringResponse(` - { "data": { "repository": { - "parent": { - "owner": {"login": "hubot"}, - "name": "ORIG" - } - } } } - `)) + { "data": { "repository": { + "name": "REPO", + "owner": { + "login": "OWNER" + }, + "parent": { + "name": "ORIG", + "owner": { + "login": "hubot" + } + } + } } } + `)) httpClient := &http.Client{Transport: reg} @@ -155,6 +169,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(` diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 2402ccbc1..8ab347594 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 } 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. 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")