github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/repo/list/http.go (about) 1 package list 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "github.com/cli/cli/api" 9 "github.com/cli/cli/pkg/githubsearch" 10 "github.com/shurcooL/githubv4" 11 ) 12 13 type RepositoryList struct { 14 Owner string 15 Repositories []api.Repository 16 TotalCount int 17 FromSearch bool 18 } 19 20 type FilterOptions struct { 21 Visibility string // private, public 22 Fork bool 23 Source bool 24 Language string 25 Topic string 26 Archived bool 27 NonArchived bool 28 Fields []string 29 } 30 31 func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) { 32 if filter.Language != "" || filter.Archived || filter.NonArchived || filter.Topic != "" { 33 return searchRepos(client, hostname, limit, owner, filter) 34 } 35 36 perPage := limit 37 if perPage > 100 { 38 perPage = 100 39 } 40 41 variables := map[string]interface{}{ 42 "perPage": githubv4.Int(perPage), 43 } 44 45 if filter.Visibility != "" { 46 variables["privacy"] = githubv4.RepositoryPrivacy(strings.ToUpper(filter.Visibility)) 47 } 48 49 if filter.Fork { 50 variables["fork"] = githubv4.Boolean(true) 51 } else if filter.Source { 52 variables["fork"] = githubv4.Boolean(false) 53 } 54 55 inputs := []string{"$perPage:Int!", "$endCursor:String", "$privacy:RepositoryPrivacy", "$fork:Boolean"} 56 var ownerConnection string 57 if owner == "" { 58 ownerConnection = "repositoryOwner: viewer" 59 } else { 60 ownerConnection = "repositoryOwner(login: $owner)" 61 variables["owner"] = githubv4.String(owner) 62 inputs = append(inputs, "$owner:String!") 63 } 64 65 type result struct { 66 RepositoryOwner struct { 67 Login string 68 Repositories struct { 69 Nodes []api.Repository 70 TotalCount int 71 PageInfo struct { 72 HasNextPage bool 73 EndCursor string 74 } 75 } 76 } 77 } 78 79 query := fmt.Sprintf(`query RepositoryList(%s) { 80 %s { 81 login 82 repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC }) { 83 nodes{%s} 84 totalCount 85 pageInfo{hasNextPage,endCursor} 86 } 87 } 88 }`, strings.Join(inputs, ","), ownerConnection, api.RepositoryGraphQL(filter.Fields)) 89 90 apiClient := api.NewClientFromHTTP(client) 91 listResult := RepositoryList{} 92 pagination: 93 for { 94 var res result 95 err := apiClient.GraphQL(hostname, query, variables, &res) 96 if err != nil { 97 return nil, err 98 } 99 100 owner := res.RepositoryOwner 101 listResult.TotalCount = owner.Repositories.TotalCount 102 listResult.Owner = owner.Login 103 104 for _, repo := range owner.Repositories.Nodes { 105 listResult.Repositories = append(listResult.Repositories, repo) 106 if len(listResult.Repositories) >= limit { 107 break pagination 108 } 109 } 110 111 if !owner.Repositories.PageInfo.HasNextPage { 112 break 113 } 114 variables["endCursor"] = githubv4.String(owner.Repositories.PageInfo.EndCursor) 115 } 116 117 return &listResult, nil 118 } 119 120 func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) { 121 type result struct { 122 Search struct { 123 RepositoryCount int 124 Nodes []api.Repository 125 PageInfo struct { 126 HasNextPage bool 127 EndCursor string 128 } 129 } 130 } 131 132 query := fmt.Sprintf(`query RepositoryListSearch($query:String!,$perPage:Int!,$endCursor:String) { 133 search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor) { 134 repositoryCount 135 nodes{...on Repository{%s}} 136 pageInfo{hasNextPage,endCursor} 137 } 138 }`, api.RepositoryGraphQL(filter.Fields)) 139 140 perPage := limit 141 if perPage > 100 { 142 perPage = 100 143 } 144 145 variables := map[string]interface{}{ 146 "query": githubv4.String(searchQuery(owner, filter)), 147 "perPage": githubv4.Int(perPage), 148 } 149 150 apiClient := api.NewClientFromHTTP(client) 151 listResult := RepositoryList{FromSearch: true} 152 pagination: 153 for { 154 var result result 155 err := apiClient.GraphQL(hostname, query, variables, &result) 156 if err != nil { 157 return nil, err 158 } 159 160 listResult.TotalCount = result.Search.RepositoryCount 161 for _, repo := range result.Search.Nodes { 162 if listResult.Owner == "" && repo.NameWithOwner != "" { 163 idx := strings.IndexRune(repo.NameWithOwner, '/') 164 listResult.Owner = repo.NameWithOwner[:idx] 165 } 166 listResult.Repositories = append(listResult.Repositories, repo) 167 if len(listResult.Repositories) >= limit { 168 break pagination 169 } 170 } 171 172 if !result.Search.PageInfo.HasNextPage { 173 break 174 } 175 variables["endCursor"] = githubv4.String(result.Search.PageInfo.EndCursor) 176 } 177 178 return &listResult, nil 179 } 180 181 func searchQuery(owner string, filter FilterOptions) string { 182 q := githubsearch.NewQuery() 183 q.SortBy(githubsearch.UpdatedAt, githubsearch.Desc) 184 185 if owner == "" { 186 q.OwnedBy("@me") 187 } else { 188 q.OwnedBy(owner) 189 } 190 191 if filter.Fork { 192 q.OnlyForks() 193 } else { 194 q.IncludeForks(!filter.Source) 195 } 196 197 if filter.Language != "" { 198 q.SetLanguage(filter.Language) 199 } 200 201 if filter.Topic != "" { 202 q.SetTopic(filter.Topic) 203 } 204 205 switch filter.Visibility { 206 case "public": 207 q.SetVisibility(githubsearch.Public) 208 case "private": 209 q.SetVisibility(githubsearch.Private) 210 } 211 212 if filter.Archived { 213 q.SetArchived(true) 214 } else if filter.NonArchived { 215 q.SetArchived(false) 216 } 217 218 return q.String() 219 }