diff --git a/pkg/cmd/repo/list/fixtures/repoList.json b/pkg/cmd/repo/list/fixtures/repoList.json index ca0faa85c..8ee348f57 100644 --- a/pkg/cmd/repo/list/fixtures/repoList.json +++ b/pkg/cmd/repo/list/fixtures/repoList.json @@ -10,7 +10,7 @@ "isFork": false, "isPrivate": false, "isArchived": false, - "updatedAt": "2021-02-19T06:34:58Z" + "pushedAt": "2021-02-19T06:34:58Z" }, { "nameWithOwner": "octocat/cli", @@ -18,7 +18,7 @@ "isFork": true, "isPrivate": false, "isArchived": false, - "updatedAt": "2021-02-19T06:06:06Z" + "pushedAt": "2021-02-19T06:06:06Z" }, { "nameWithOwner": "octocat/testing", @@ -26,7 +26,7 @@ "isFork": false, "isPrivate": true, "isArchived": false, - "updatedAt": "2021-02-11T22:32:05Z" + "pushedAt": "2021-02-11T22:32:05Z" } ], "pageInfo": { diff --git a/pkg/cmd/repo/list/http.go b/pkg/cmd/repo/list/http.go index 4669e72a3..8413b479f 100644 --- a/pkg/cmd/repo/list/http.go +++ b/pkg/cmd/repo/list/http.go @@ -1,59 +1,62 @@ package list import ( + "context" + "net/http" "strings" + "time" - "github.com/cli/cli/api" + "github.com/cli/cli/internal/ghinstance" "github.com/shurcooL/githubv4" + "github.com/shurcooL/graphql" ) +type Repository struct { + NameWithOwner string + Description string + IsFork bool + IsPrivate bool + IsArchived bool + PushedAt time.Time +} + +func (r Repository) Info() string { + var tags []string + + if r.IsPrivate { + tags = append(tags, "private") + } else { + tags = append(tags, "public") + } + if r.IsFork { + tags = append(tags, "fork") + } + if r.IsArchived { + tags = append(tags, "archived") + } + + return strings.Join(tags, ", ") +} + type RepositoryList struct { Repositories []Repository TotalCount int } -func listRepos(client *api.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) { - type response struct { +func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) { + type query struct { RepositoryOwner struct { Repositories struct { - TotalCount int - RepositoryCount int - Nodes []Repository - PageInfo struct { + Nodes []Repository + TotalCount int + PageInfo struct { HasNextPage bool EndCursor string } - } - } + } `graphql:"repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC })"` + } `graphql:"repositoryOwner(login: $owner)"` } - query := ` - query RepoList($owner: String!, $per_page: Int!, $endCursor: String, $fork: Boolean, $privacy: RepositoryPrivacy) { - repositoryOwner(login: $owner) { - repositories( - first: $per_page, - after: $endCursor, - privacy: $privacy, - isFork: $fork, - ownerAffiliations: OWNER, - orderBy: { field: UPDATED_AT, direction: DESC }) { - totalCount - nodes { - nameWithOwner - description - isFork - isPrivate - isArchived - updatedAt - } - pageInfo { - hasNextPage - endCursor - } - } - } - }` - perPage := limit if perPage > 100 { perPage = 100 @@ -61,7 +64,7 @@ func listRepos(client *api.Client, hostname string, limit int, owner string, fil variables := map[string]interface{}{ "owner": githubv4.String(owner), - "per_page": githubv4.Int(perPage), + "perPage": githubv4.Int(perPage), "endCursor": (*githubv4.String)(nil), } @@ -79,40 +82,29 @@ func listRepos(client *api.Client, hostname string, limit int, owner string, fil variables["fork"] = (*githubv4.Boolean)(nil) } - var repos []Repository - var totalCount int - - var result response - + listResult := RepositoryList{} pagination: for { - err := client.GraphQL(hostname, query, variables, &result) + var result query + gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client) + err := gql.QueryNamed(context.Background(), "RepositoryList", &result, variables) if err != nil { return nil, err } - repos = append(repos, result.RepositoryOwner.Repositories.Nodes...) - - if len(repos) >= limit { - if len(repos) > limit { - repos = repos[:limit] + listResult.TotalCount = result.RepositoryOwner.Repositories.TotalCount + for _, repo := range result.RepositoryOwner.Repositories.Nodes { + listResult.Repositories = append(listResult.Repositories, repo) + if len(listResult.Repositories) >= limit { + break pagination } - break pagination } if !result.RepositoryOwner.Repositories.PageInfo.HasNextPage { break } - variables["endCursor"] = githubv4.String(result.RepositoryOwner.Repositories.PageInfo.EndCursor) } - totalCount = result.RepositoryOwner.Repositories.TotalCount - - listResult := &RepositoryList{ - Repositories: repos, - TotalCount: totalCount, - } - - return listResult, nil + return &listResult, nil } diff --git a/pkg/cmd/repo/list/list.go b/pkg/cmd/repo/list/list.go index 631a50bf3..4fd74f827 100644 --- a/pkg/cmd/repo/list/list.go +++ b/pkg/cmd/repo/list/list.go @@ -3,7 +3,6 @@ package list import ( "fmt" "net/http" - "strings" "time" "github.com/cli/cli/api" @@ -45,14 +44,12 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman var ( flagPublic bool flagPrivate bool - flagSource bool - flagFork bool ) cmd := &cobra.Command{ - Use: "list", + Use: "list []", Args: cobra.MaximumNArgs(1), - Short: "List repositories from a user or organization", + Short: "List repositories owned by user or organization", RunE: func(c *cobra.Command, args []string) error { if opts.Limit < 1 { return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", opts.Limit)} @@ -61,7 +58,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman if flagPrivate && flagPublic { return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--public` or `--private`")} } - if flagSource && flagFork { + if opts.Source && opts.Fork { return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--source` or `--fork`")} } @@ -71,9 +68,6 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman opts.Visibility = "public" } - opts.Fork = flagFork - opts.Source = flagSource - if len(args) > 0 { opts.Owner = args[0] } @@ -88,8 +82,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 30, "Maximum number of repositories to list") cmd.Flags().BoolVar(&flagPrivate, "private", false, "Show only private repositories") cmd.Flags().BoolVar(&flagPublic, "public", false, "Show only public repositories") - cmd.Flags().BoolVar(&flagSource, "source", false, "Show only source repositories") - cmd.Flags().BoolVar(&flagFork, "fork", false, "Show only forks") + cmd.Flags().BoolVar(&opts.Source, "source", false, "Show only non-forks") + cmd.Flags().BoolVar(&opts.Fork, "fork", false, "Show only forks") return cmd } @@ -118,58 +112,36 @@ func listRun(opts *ListOptions) error { Source: opts.Source, } - listResult, err := listRepos(apiClient, ghinstance.OverridableDefault(), opts.Limit, owner, filter) + listResult, err := listRepos(httpClient, ghinstance.OverridableDefault(), opts.Limit, owner, filter) if err != nil { return err } cs := opts.IO.ColorScheme() - tp := utils.NewTablePrinter(opts.IO) - - notArchived := filter.Fork || filter.Source - - matchCount := len(listResult.Repositories) now := opts.Now() for _, repo := range listResult.Repositories { - if notArchived && repo.IsArchived { - matchCount-- - listResult.TotalCount-- - continue - } - - nameWithOwner := repo.NameWithOwner - info := repo.Info() infoColor := cs.Gray - visibility := "Public" - if repo.IsPrivate { infoColor = cs.Yellow - visibility = "Private" } - description := repo.Description - updatedAt := repo.UpdatedAt.Format(time.RFC3339) - + tp.AddField(repo.NameWithOwner, nil, cs.Bold) + tp.AddField(text.ReplaceExcessiveWhitespace(repo.Description), nil, nil) + tp.AddField(info, nil, infoColor) if tp.IsTTY() { - tp.AddField(nameWithOwner, nil, cs.Bold) - tp.AddField(text.ReplaceExcessiveWhitespace(description), nil, nil) - tp.AddField(info, nil, infoColor) - tp.AddField(utils.FuzzyAgoAbbr(now, repo.UpdatedAt), nil, nil) + tp.AddField(utils.FuzzyAgoAbbr(now, repo.PushedAt), nil, cs.Gray) } else { - tp.AddField(nameWithOwner, nil, nil) - tp.AddField(text.ReplaceExcessiveWhitespace(description), nil, nil) - tp.AddField(visibility, nil, nil) - tp.AddField(updatedAt, nil, nil) + tp.AddField(repo.PushedAt.Format(time.RFC3339), nil, nil) } tp.EndRow() } if isTerminal { hasFilters := filter.Visibility != "" || filter.Fork || filter.Source - title := listHeader(owner, matchCount, listResult.TotalCount, hasFilters) + title := listHeader(owner, len(listResult.Repositories), listResult.TotalCount, hasFilters) fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title) } @@ -186,34 +158,3 @@ func listHeader(owner string, matchCount, totalMatchCount int, hasFilters bool) return fmt.Sprintf("Showing %d of %d repositories in @%s", matchCount, totalMatchCount, owner) } - -type Repository struct { - NameWithOwner string - Description string - IsFork bool - IsPrivate bool - IsArchived bool - UpdatedAt time.Time -} - -func (r Repository) Info() string { - var info string - var tags []string - - if r.IsPrivate { - tags = append(tags, "private") - } - if r.IsFork { - tags = append(tags, "fork") - } - if r.IsArchived { - tags = append(tags, "archived") - } - - if len(tags) > 0 { - tags[0] = strings.Title(tags[0]) - info = strings.Join(tags, ", ") - } - - return info -} diff --git a/pkg/cmd/repo/list/list_test.go b/pkg/cmd/repo/list/list_test.go index 7ffe6de17..d6bc37afe 100644 --- a/pkg/cmd/repo/list/list_test.go +++ b/pkg/cmd/repo/list/list_test.go @@ -62,7 +62,7 @@ func TestRepoList_nontty(t *testing.T) { httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`), ) httpReg.Register( - httpmock.GraphQL(`query RepoList\b`), + httpmock.GraphQL(`query RepositoryList\b`), httpmock.FileResponse("./fixtures/repoList.json"), ) @@ -84,9 +84,9 @@ func TestRepoList_nontty(t *testing.T) { assert.Equal(t, "", stderr.String()) assert.Equal(t, heredoc.Doc(` - octocat/hello-world My first repository Public 2021-02-19T06:34:58Z - octocat/cli GitHub CLI Public 2021-02-19T06:06:06Z - octocat/testing Private 2021-02-11T22:32:05Z + octocat/hello-world My first repository public 2021-02-19T06:34:58Z + octocat/cli GitHub CLI public, fork 2021-02-19T06:06:06Z + octocat/testing private 2021-02-11T22:32:05Z `), stdout.String()) } @@ -104,7 +104,7 @@ func TestRepoList_tty(t *testing.T) { httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`), ) httpReg.Register( - httpmock.GraphQL(`query RepoList\b`), + httpmock.GraphQL(`query RepositoryList\b`), httpmock.FileResponse("./fixtures/repoList.json"), ) @@ -129,9 +129,9 @@ func TestRepoList_tty(t *testing.T) { Showing 3 of 3 repositories in @octocat - octocat/hello-world My first repository 8h - octocat/cli GitHub CLI Fork 8h - octocat/testing Private 7d + octocat/hello-world My first repository public 8h + octocat/cli GitHub CLI public, fork 8h + octocat/testing private 7d `), stdout.String()) } @@ -144,10 +144,10 @@ func TestRepoList_filtering(t *testing.T) { httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`), ) http.Register( - httpmock.GraphQL(`query RepoList\b`), + httpmock.GraphQL(`query RepositoryList\b`), httpmock.GraphQLQuery(`{}`, func(_ string, params map[string]interface{}) { assert.Equal(t, "PRIVATE", params["privacy"]) - assert.Equal(t, float64(2), params["per_page"]) + assert.Equal(t, float64(2), params["perPage"]) }), )