Skip to content

Commit a2307e3

Browse files
committed
Add repo list --json support
1 parent 02a2ed2 commit a2307e3

File tree

2 files changed

+94
-83
lines changed

2 files changed

+94
-83
lines changed

pkg/cmd/repo/list/http.go

Lines changed: 57 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,18 @@
11
package list
22

33
import (
4-
"context"
4+
"fmt"
55
"net/http"
6-
"reflect"
76
"strings"
8-
"time"
97

10-
"github.com/cli/cli/internal/ghinstance"
8+
"github.com/cli/cli/api"
119
"github.com/cli/cli/pkg/githubsearch"
1210
"github.com/shurcooL/githubv4"
13-
"github.com/shurcooL/graphql"
1411
)
1512

16-
type Repository struct {
17-
NameWithOwner string
18-
Description string
19-
IsFork bool
20-
IsPrivate bool
21-
IsArchived bool
22-
PushedAt time.Time
23-
}
24-
25-
func (r Repository) Info() string {
26-
var tags []string
27-
28-
if r.IsPrivate {
29-
tags = append(tags, "private")
30-
} else {
31-
tags = append(tags, "public")
32-
}
33-
if r.IsFork {
34-
tags = append(tags, "fork")
35-
}
36-
if r.IsArchived {
37-
tags = append(tags, "archived")
38-
}
39-
40-
return strings.Join(tags, ", ")
41-
}
42-
4313
type RepositoryList struct {
4414
Owner string
45-
Repositories []Repository
15+
Repositories []api.Repository
4616
TotalCount int
4717
FromSearch bool
4818
}
@@ -54,6 +24,7 @@ type FilterOptions struct {
5424
Language string
5525
Archived bool
5626
NonArchived bool
27+
Fields []string
5728
}
5829

5930
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
6738
}
6839

6940
variables := map[string]interface{}{
70-
"perPage": githubv4.Int(perPage),
71-
"endCursor": (*githubv4.String)(nil),
41+
"perPage": githubv4.Int(perPage),
7242
}
7343

7444
if filter.Visibility != "" {
7545
variables["privacy"] = githubv4.RepositoryPrivacy(strings.ToUpper(filter.Visibility))
76-
} else {
77-
variables["privacy"] = (*githubv4.RepositoryPrivacy)(nil)
7846
}
7947

8048
if filter.Fork {
8149
variables["fork"] = githubv4.Boolean(true)
8250
} else if filter.Source {
8351
variables["fork"] = githubv4.Boolean(false)
84-
} else {
85-
variables["fork"] = (*githubv4.Boolean)(nil)
8652
}
8753

54+
inputs := []string{"$perPage:Int!", "$endCursor:String", "$privacy:RepositoryPrivacy", "$fork:Boolean"}
8855
var ownerConnection string
8956
if owner == "" {
90-
ownerConnection = `graphql:"repositoryOwner: viewer"`
57+
ownerConnection = "repositoryOwner: viewer"
9158
} else {
92-
ownerConnection = `graphql:"repositoryOwner(login: $owner)"`
59+
ownerConnection = "repositoryOwner(login: $owner)"
9360
variables["owner"] = githubv4.String(owner)
61+
inputs = append(inputs, "$owner:String!")
62+
}
63+
64+
type result struct {
65+
RepositoryOwner struct {
66+
Login string
67+
Repositories struct {
68+
Nodes []api.Repository
69+
TotalCount int
70+
PageInfo struct {
71+
HasNextPage bool
72+
EndCursor string
73+
}
74+
}
75+
}
9476
}
9577

96-
type repositoryOwner struct {
97-
Login string
98-
Repositories struct {
99-
Nodes []Repository
100-
TotalCount int
101-
PageInfo struct {
102-
HasNextPage bool
103-
EndCursor string
78+
query := fmt.Sprintf(`query RepositoryList(%s) {
79+
%s {
80+
login
81+
repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC }) {
82+
nodes{%s}
83+
totalCount
84+
pageInfo{hasNextPage,endCursor}
10485
}
105-
} `graphql:"repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC })"`
106-
}
107-
query := reflect.StructOf([]reflect.StructField{
108-
{
109-
Name: "RepositoryOwner",
110-
Type: reflect.TypeOf(repositoryOwner{}),
111-
Tag: reflect.StructTag(ownerConnection),
112-
},
113-
})
86+
}
87+
}`, strings.Join(inputs, ","), ownerConnection, api.RepositoryGraphQL(filter.Fields))
11488

115-
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
89+
apiClient := api.NewClientFromHTTP(client)
11690
listResult := RepositoryList{}
11791
pagination:
11892
for {
119-
result := reflect.New(query)
120-
err := gql.QueryNamed(context.Background(), "RepositoryList", result.Interface(), variables)
93+
var res result
94+
err := apiClient.GraphQL(hostname, query, variables, &res)
12195
if err != nil {
12296
return nil, err
12397
}
12498

125-
owner := result.Elem().FieldByName("RepositoryOwner").Interface().(repositoryOwner)
99+
owner := res.RepositoryOwner
126100
listResult.TotalCount = owner.Repositories.TotalCount
127101
listResult.Owner = owner.Login
128102

@@ -143,47 +117,52 @@ pagination:
143117
}
144118

145119
func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
146-
type query struct {
120+
type result struct {
147121
Search struct {
148122
RepositoryCount int
149-
Nodes []struct {
150-
Repository Repository `graphql:"...on Repository"`
151-
}
152-
PageInfo struct {
123+
Nodes []api.Repository
124+
PageInfo struct {
153125
HasNextPage bool
154126
EndCursor string
155127
}
156-
} `graphql:"search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor)"`
128+
}
157129
}
158130

131+
query := fmt.Sprintf(`query RepositoryListSearch($query:String!,$perPage:Int!,$endCursor:String) {
132+
search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor) {
133+
repositoryCount
134+
nodes{...on Repository{%s}}
135+
pageInfo{hasNextPage,endCursor}
136+
}
137+
}`, api.RepositoryGraphQL(filter.Fields))
138+
159139
perPage := limit
160140
if perPage > 100 {
161141
perPage = 100
162142
}
163143

164144
variables := map[string]interface{}{
165-
"query": githubv4.String(searchQuery(owner, filter)),
166-
"perPage": githubv4.Int(perPage),
167-
"endCursor": (*githubv4.String)(nil),
145+
"query": githubv4.String(searchQuery(owner, filter)),
146+
"perPage": githubv4.Int(perPage),
168147
}
169148

170-
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
149+
apiClient := api.NewClientFromHTTP(client)
171150
listResult := RepositoryList{FromSearch: true}
172151
pagination:
173152
for {
174-
var result query
175-
err := gql.QueryNamed(context.Background(), "RepositoryListSearch", &result, variables)
153+
var result result
154+
err := apiClient.GraphQL(hostname, query, variables, &result)
176155
if err != nil {
177156
return nil, err
178157
}
179158

180159
listResult.TotalCount = result.Search.RepositoryCount
181-
for _, node := range result.Search.Nodes {
160+
for _, repo := range result.Search.Nodes {
182161
if listResult.Owner == "" {
183-
idx := strings.IndexRune(node.Repository.NameWithOwner, '/')
184-
listResult.Owner = node.Repository.NameWithOwner[:idx]
162+
idx := strings.IndexRune(repo.NameWithOwner, '/')
163+
listResult.Owner = repo.NameWithOwner[:idx]
185164
}
186-
listResult.Repositories = append(listResult.Repositories, node.Repository)
165+
listResult.Repositories = append(listResult.Repositories, repo)
187166
if len(listResult.Repositories) >= limit {
188167
break pagination
189168
}

pkg/cmd/repo/list/list.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package list
33
import (
44
"fmt"
55
"net/http"
6+
"strings"
67
"time"
78

9+
"github.com/cli/cli/api"
810
"github.com/cli/cli/internal/config"
911
"github.com/cli/cli/pkg/cmdutil"
1012
"github.com/cli/cli/pkg/iostreams"
@@ -17,6 +19,7 @@ type ListOptions struct {
1719
HttpClient func() (*http.Client, error)
1820
Config func() (config.Config, error)
1921
IO *iostreams.IOStreams
22+
Exporter cmdutil.Exporter
2023

2124
Limit int
2225
Owner string
@@ -88,10 +91,13 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
8891
cmd.Flags().StringVarP(&opts.Language, "language", "l", "", "Filter by primary coding language")
8992
cmd.Flags().BoolVar(&opts.Archived, "archived", false, "Show only archived repositories")
9093
cmd.Flags().BoolVar(&opts.NonArchived, "no-archived", false, "Omit archived repositories")
94+
cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.RepositoryFields)
9195

9296
return cmd
9397
}
9498

99+
var defaultFields = []string{"nameWithOwner", "description", "isPrivate", "isFork", "isArchived", "createdAt", "pushedAt"}
100+
95101
func listRun(opts *ListOptions) error {
96102
httpClient, err := opts.HttpClient()
97103
if err != nil {
@@ -105,6 +111,10 @@ func listRun(opts *ListOptions) error {
105111
Language: opts.Language,
106112
Archived: opts.Archived,
107113
NonArchived: opts.NonArchived,
114+
Fields: defaultFields,
115+
}
116+
if opts.Exporter != nil {
117+
filter.Fields = opts.Exporter.Fields()
108118
}
109119

110120
cfg, err := opts.Config()
@@ -127,27 +137,31 @@ func listRun(opts *ListOptions) error {
127137
}
128138
defer opts.IO.StopPager()
129139

140+
if opts.Exporter != nil {
141+
return opts.Exporter.Write(opts.IO.Out, listResult.Repositories, opts.IO.ColorEnabled())
142+
}
143+
130144
cs := opts.IO.ColorScheme()
131145
tp := utils.NewTablePrinter(opts.IO)
132146
now := opts.Now()
133147

134148
for _, repo := range listResult.Repositories {
135-
info := repo.Info()
149+
info := repoInfo(repo)
136150
infoColor := cs.Gray
137151
if repo.IsPrivate {
138152
infoColor = cs.Yellow
139153
}
140154

141155
t := repo.PushedAt
142-
// if listResult.FromSearch {
143-
// t = repo.UpdatedAt
144-
// }
156+
if repo.PushedAt == nil {
157+
t = &repo.CreatedAt
158+
}
145159

146160
tp.AddField(repo.NameWithOwner, nil, cs.Bold)
147161
tp.AddField(text.ReplaceExcessiveWhitespace(repo.Description), nil, nil)
148162
tp.AddField(info, nil, infoColor)
149163
if tp.IsTTY() {
150-
tp.AddField(utils.FuzzyAgoAbbr(now, t), nil, cs.Gray)
164+
tp.AddField(utils.FuzzyAgoAbbr(now, *t), nil, cs.Gray)
151165
} else {
152166
tp.AddField(t.Format(time.RFC3339), nil, nil)
153167
}
@@ -179,3 +193,21 @@ func listHeader(owner string, matchCount, totalMatchCount int, hasFilters bool)
179193
}
180194
return fmt.Sprintf("Showing %d of %d repositories in @%s%s", matchCount, totalMatchCount, owner, matchStr)
181195
}
196+
197+
func repoInfo(r api.Repository) string {
198+
var tags []string
199+
200+
if r.IsPrivate {
201+
tags = append(tags, "private")
202+
} else {
203+
tags = append(tags, "public")
204+
}
205+
if r.IsFork {
206+
tags = append(tags, "fork")
207+
}
208+
if r.IsArchived {
209+
tags = append(tags, "archived")
210+
}
211+
212+
return strings.Join(tags, ", ")
213+
}

0 commit comments

Comments
 (0)