235 lines
5.4 KiB
Go
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()
|
|
}
|