cli/pkg/cmd/repo/list/http.go
2021-03-23 18:52:58 +01:00

235 lines
5.4 KiB
Go

package list
import (
"context"
"net/http"
"reflect"
"strings"
"time"
"github.com/cli/cli/internal/ghinstance"
"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
TotalCount int
FromSearch bool
}
type FilterOptions struct {
Visibility string // private, public
Fork bool
Source bool
Language string
Archived bool
NonArchived bool
}
func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
if filter.Language != "" || filter.Archived || filter.NonArchived {
return searchRepos(client, hostname, limit, owner, filter)
}
perPage := limit
if perPage > 100 {
perPage = 100
}
variables := map[string]interface{}{
"perPage": githubv4.Int(perPage),
"endCursor": (*githubv4.String)(nil),
}
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)
}
var ownerConnection string
if owner == "" {
ownerConnection = `graphql:"repositoryOwner: viewer"`
} else {
ownerConnection = `graphql:"repositoryOwner(login: $owner)"`
variables["owner"] = githubv4.String(owner)
}
type repositoryOwner struct {
Login string
Repositories 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 })"`
}
query := reflect.StructOf([]reflect.StructField{
{
Name: "RepositoryOwner",
Type: reflect.TypeOf(repositoryOwner{}),
Tag: reflect.StructTag(ownerConnection),
},
})
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
listResult := RepositoryList{}
pagination:
for {
result := reflect.New(query)
err := gql.QueryNamed(context.Background(), "RepositoryList", result.Interface(), variables)
if err != nil {
return nil, err
}
owner := result.Elem().FieldByName("RepositoryOwner").Interface().(repositoryOwner)
listResult.TotalCount = owner.Repositories.TotalCount
listResult.Owner = owner.Login
for _, repo := range owner.Repositories.Nodes {
listResult.Repositories = append(listResult.Repositories, repo)
if len(listResult.Repositories) >= limit {
break pagination
}
}
if !owner.Repositories.PageInfo.HasNextPage {
break
}
variables["endCursor"] = githubv4.String(owner.Repositories.PageInfo.EndCursor)
}
return &listResult, nil
}
func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
type query struct {
Search struct {
RepositoryCount int
Nodes []struct {
Repository Repository `graphql:"...on Repository"`
}
PageInfo struct {
HasNextPage bool
EndCursor string
}
} `graphql:"search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor)"`
}
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),
}
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
listResult := RepositoryList{FromSearch: true}
pagination:
for {
var result query
err := gql.QueryNamed(context.Background(), "RepositoryListSearch", &result, variables)
if err != nil {
return nil, err
}
listResult.TotalCount = result.Search.RepositoryCount
for _, node := range result.Search.Nodes {
if listResult.Owner == "" {
idx := strings.IndexRune(node.Repository.NameWithOwner, '/')
listResult.Owner = node.Repository.NameWithOwner[:idx]
}
listResult.Repositories = append(listResult.Repositories, node.Repository)
if len(listResult.Repositories) >= limit {
break pagination
}
}
if !result.Search.PageInfo.HasNextPage {
break
}
variables["endCursor"] = githubv4.String(result.Search.PageInfo.EndCursor)
}
return &listResult, nil
}
func searchQuery(owner string, filter FilterOptions) string {
q := githubsearch.NewQuery()
q.SortBy(githubsearch.UpdatedAt, githubsearch.Desc)
if owner == "" {
q.OwnedBy("@me")
} else {
q.OwnedBy(owner)
}
if filter.Fork {
q.OnlyForks()
} else {
q.IncludeForks(!filter.Source)
}
if filter.Language != "" {
q.SetLanguage(filter.Language)
}
switch filter.Visibility {
case "public":
q.SetVisibility(githubsearch.Public)
case "private":
q.SetVisibility(githubsearch.Private)
}
if filter.Archived {
q.SetArchived(true)
} else if filter.NonArchived {
q.SetArchived(false)
}
return q.String()
}