Merge remote-tracking branch 'origin' into fix855
This commit is contained in:
commit
ec64363085
36 changed files with 971 additions and 318 deletions
28
.github/CONTRIBUTING.md
vendored
28
.github/CONTRIBUTING.md
vendored
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
4
Makefile
4
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)
|
||||
|
|
|
|||
45
README.md
45
README.md
|
|
@ -4,21 +4,15 @@
|
|||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
<!-- this anchor is linked to from elsewhere, so avoid renaming it -->
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)"`
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
3
go.mod
3
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
|
||||
)
|
||||
|
||||
|
|
|
|||
179
go.sum
179
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=
|
||||
|
|
|
|||
19
internal/build/build.go
Normal file
19
internal/build/build.go
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 == "" {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue