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  }