Add repo list --json support

This commit is contained in:
Mislav Marohnić 2021-05-17 16:32:01 +02:00
parent 02a2ed2f73
commit a2307e357d
2 changed files with 93 additions and 82 deletions

View file

@ -1,48 +1,18 @@
package list
import (
"context"
"fmt"
"net/http"
"reflect"
"strings"
"time"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/api"
"github.com/cli/cli/pkg/githubsearch"
"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 {
Owner string
Repositories []Repository
Repositories []api.Repository
TotalCount int
FromSearch bool
}
@ -54,6 +24,7 @@ type FilterOptions struct {
Language string
Archived bool
NonArchived bool
Fields []string
}
func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
@ -67,62 +38,65 @@ func listRepos(client *http.Client, hostname string, limit int, owner string, fi
}
variables := map[string]interface{}{
"perPage": githubv4.Int(perPage),
"endCursor": (*githubv4.String)(nil),
"perPage": githubv4.Int(perPage),
}
if filter.Visibility != "" {
variables["privacy"] = githubv4.RepositoryPrivacy(strings.ToUpper(filter.Visibility))
} else {
variables["privacy"] = (*githubv4.RepositoryPrivacy)(nil)
}
if filter.Fork {
variables["fork"] = githubv4.Boolean(true)
} else if filter.Source {
variables["fork"] = githubv4.Boolean(false)
} else {
variables["fork"] = (*githubv4.Boolean)(nil)
}
inputs := []string{"$perPage:Int!", "$endCursor:String", "$privacy:RepositoryPrivacy", "$fork:Boolean"}
var ownerConnection string
if owner == "" {
ownerConnection = `graphql:"repositoryOwner: viewer"`
ownerConnection = "repositoryOwner: viewer"
} else {
ownerConnection = `graphql:"repositoryOwner(login: $owner)"`
ownerConnection = "repositoryOwner(login: $owner)"
variables["owner"] = githubv4.String(owner)
inputs = append(inputs, "$owner:String!")
}
type repositoryOwner struct {
Login string
Repositories struct {
Nodes []Repository
TotalCount int
PageInfo struct {
HasNextPage bool
EndCursor string
type result struct {
RepositoryOwner struct {
Login string
Repositories struct {
Nodes []api.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 })"`
}
}
query := reflect.StructOf([]reflect.StructField{
{
Name: "RepositoryOwner",
Type: reflect.TypeOf(repositoryOwner{}),
Tag: reflect.StructTag(ownerConnection),
},
})
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
query := fmt.Sprintf(`query RepositoryList(%s) {
%s {
login
repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC }) {
nodes{%s}
totalCount
pageInfo{hasNextPage,endCursor}
}
}
}`, strings.Join(inputs, ","), ownerConnection, api.RepositoryGraphQL(filter.Fields))
apiClient := api.NewClientFromHTTP(client)
listResult := RepositoryList{}
pagination:
for {
result := reflect.New(query)
err := gql.QueryNamed(context.Background(), "RepositoryList", result.Interface(), variables)
var res result
err := apiClient.GraphQL(hostname, query, variables, &res)
if err != nil {
return nil, err
}
owner := result.Elem().FieldByName("RepositoryOwner").Interface().(repositoryOwner)
owner := res.RepositoryOwner
listResult.TotalCount = owner.Repositories.TotalCount
listResult.Owner = owner.Login
@ -143,47 +117,52 @@ pagination:
}
func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
type query struct {
type result struct {
Search struct {
RepositoryCount int
Nodes []struct {
Repository Repository `graphql:"...on Repository"`
}
PageInfo struct {
Nodes []api.Repository
PageInfo struct {
HasNextPage bool
EndCursor string
}
} `graphql:"search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor)"`
}
}
query := fmt.Sprintf(`query RepositoryListSearch($query:String!,$perPage:Int!,$endCursor:String) {
search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor) {
repositoryCount
nodes{...on Repository{%s}}
pageInfo{hasNextPage,endCursor}
}
}`, api.RepositoryGraphQL(filter.Fields))
perPage := limit
if perPage > 100 {
perPage = 100
}
variables := map[string]interface{}{
"query": githubv4.String(searchQuery(owner, filter)),
"perPage": githubv4.Int(perPage),
"endCursor": (*githubv4.String)(nil),
"query": githubv4.String(searchQuery(owner, filter)),
"perPage": githubv4.Int(perPage),
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
apiClient := api.NewClientFromHTTP(client)
listResult := RepositoryList{FromSearch: true}
pagination:
for {
var result query
err := gql.QueryNamed(context.Background(), "RepositoryListSearch", &result, variables)
var result result
err := apiClient.GraphQL(hostname, query, variables, &result)
if err != nil {
return nil, err
}
listResult.TotalCount = result.Search.RepositoryCount
for _, node := range result.Search.Nodes {
for _, repo := range result.Search.Nodes {
if listResult.Owner == "" {
idx := strings.IndexRune(node.Repository.NameWithOwner, '/')
listResult.Owner = node.Repository.NameWithOwner[:idx]
idx := strings.IndexRune(repo.NameWithOwner, '/')
listResult.Owner = repo.NameWithOwner[:idx]
}
listResult.Repositories = append(listResult.Repositories, node.Repository)
listResult.Repositories = append(listResult.Repositories, repo)
if len(listResult.Repositories) >= limit {
break pagination
}

View file

@ -3,8 +3,10 @@ package list
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/config"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
@ -17,6 +19,7 @@ type ListOptions struct {
HttpClient func() (*http.Client, error)
Config func() (config.Config, error)
IO *iostreams.IOStreams
Exporter cmdutil.Exporter
Limit int
Owner string
@ -88,10 +91,13 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
cmd.Flags().StringVarP(&opts.Language, "language", "l", "", "Filter by primary coding language")
cmd.Flags().BoolVar(&opts.Archived, "archived", false, "Show only archived repositories")
cmd.Flags().BoolVar(&opts.NonArchived, "no-archived", false, "Omit archived repositories")
cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.RepositoryFields)
return cmd
}
var defaultFields = []string{"nameWithOwner", "description", "isPrivate", "isFork", "isArchived", "createdAt", "pushedAt"}
func listRun(opts *ListOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
@ -105,6 +111,10 @@ func listRun(opts *ListOptions) error {
Language: opts.Language,
Archived: opts.Archived,
NonArchived: opts.NonArchived,
Fields: defaultFields,
}
if opts.Exporter != nil {
filter.Fields = opts.Exporter.Fields()
}
cfg, err := opts.Config()
@ -127,27 +137,31 @@ func listRun(opts *ListOptions) error {
}
defer opts.IO.StopPager()
if opts.Exporter != nil {
return opts.Exporter.Write(opts.IO.Out, listResult.Repositories, opts.IO.ColorEnabled())
}
cs := opts.IO.ColorScheme()
tp := utils.NewTablePrinter(opts.IO)
now := opts.Now()
for _, repo := range listResult.Repositories {
info := repo.Info()
info := repoInfo(repo)
infoColor := cs.Gray
if repo.IsPrivate {
infoColor = cs.Yellow
}
t := repo.PushedAt
// if listResult.FromSearch {
// t = repo.UpdatedAt
// }
if repo.PushedAt == nil {
t = &repo.CreatedAt
}
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(utils.FuzzyAgoAbbr(now, t), nil, cs.Gray)
tp.AddField(utils.FuzzyAgoAbbr(now, *t), nil, cs.Gray)
} else {
tp.AddField(t.Format(time.RFC3339), nil, nil)
}
@ -179,3 +193,21 @@ func listHeader(owner string, matchCount, totalMatchCount int, hasFilters bool)
}
return fmt.Sprintf("Showing %d of %d repositories in @%s%s", matchCount, totalMatchCount, owner, matchStr)
}
func repoInfo(r api.Repository) 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, ", ")
}