github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/context/context.go (about)

     1  // TODO: rename this package to avoid clash with stdlib
     2  package context
     3  
     4  import (
     5  	"errors"
     6  	"sort"
     7  
     8  	"github.com/AlecAivazis/survey/v2"
     9  	"github.com/cli/cli/api"
    10  	"github.com/cli/cli/git"
    11  	"github.com/cli/cli/internal/ghrepo"
    12  	"github.com/cli/cli/pkg/iostreams"
    13  	"github.com/cli/cli/pkg/prompt"
    14  )
    15  
    16  // cap the number of git remotes looked up, since the user might have an
    17  // unusually large number of git remotes
    18  const maxRemotesForLookup = 5
    19  
    20  func ResolveRemotesToRepos(remotes Remotes, client *api.Client, base string) (*ResolvedRemotes, error) {
    21  	sort.Stable(remotes)
    22  
    23  	result := &ResolvedRemotes{
    24  		remotes:   remotes,
    25  		apiClient: client,
    26  	}
    27  
    28  	var baseOverride ghrepo.Interface
    29  	if base != "" {
    30  		var err error
    31  		baseOverride, err = ghrepo.FromFullName(base)
    32  		if err != nil {
    33  			return result, err
    34  		}
    35  		result.baseOverride = baseOverride
    36  	}
    37  
    38  	return result, nil
    39  }
    40  
    41  func resolveNetwork(result *ResolvedRemotes) error {
    42  	var repos []ghrepo.Interface
    43  	for _, r := range result.remotes {
    44  		repos = append(repos, r)
    45  		if len(repos) == maxRemotesForLookup {
    46  			break
    47  		}
    48  	}
    49  
    50  	networkResult, err := api.RepoNetwork(result.apiClient, repos)
    51  	result.network = &networkResult
    52  	return err
    53  }
    54  
    55  type ResolvedRemotes struct {
    56  	baseOverride ghrepo.Interface
    57  	remotes      Remotes
    58  	network      *api.RepoNetworkResult
    59  	apiClient    *api.Client
    60  }
    61  
    62  func (r *ResolvedRemotes) BaseRepo(io *iostreams.IOStreams) (ghrepo.Interface, error) {
    63  	if r.baseOverride != nil {
    64  		return r.baseOverride, nil
    65  	}
    66  
    67  	// if any of the remotes already has a resolution, respect that
    68  	for _, r := range r.remotes {
    69  		if r.Resolved == "base" {
    70  			return r, nil
    71  		} else if r.Resolved != "" {
    72  			repo, err := ghrepo.FromFullName(r.Resolved)
    73  			if err != nil {
    74  				return nil, err
    75  			}
    76  			return ghrepo.NewWithHost(repo.RepoOwner(), repo.RepoName(), r.RepoHost()), nil
    77  		}
    78  	}
    79  
    80  	if !io.CanPrompt() {
    81  		// we cannot prompt, so just resort to the 1st remote
    82  		return r.remotes[0], nil
    83  	}
    84  
    85  	// from here on, consult the API
    86  	if r.network == nil {
    87  		err := resolveNetwork(r)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  	}
    92  
    93  	var repoNames []string
    94  	repoMap := map[string]*api.Repository{}
    95  	add := func(r *api.Repository) {
    96  		fn := ghrepo.FullName(r)
    97  		if _, ok := repoMap[fn]; !ok {
    98  			repoMap[fn] = r
    99  			repoNames = append(repoNames, fn)
   100  		}
   101  	}
   102  
   103  	for _, repo := range r.network.Repositories {
   104  		if repo == nil {
   105  			continue
   106  		}
   107  		if repo.Parent != nil {
   108  			add(repo.Parent)
   109  		}
   110  		add(repo)
   111  	}
   112  
   113  	if len(repoNames) == 0 {
   114  		return r.remotes[0], nil
   115  	}
   116  
   117  	baseName := repoNames[0]
   118  	if len(repoNames) > 1 {
   119  		err := prompt.SurveyAskOne(&survey.Select{
   120  			Message: "Which should be the base repository (used for e.g. querying issues) for this directory?",
   121  			Options: repoNames,
   122  		}, &baseName)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  	}
   127  
   128  	// determine corresponding git remote
   129  	selectedRepo := repoMap[baseName]
   130  	resolution := "base"
   131  	remote, _ := r.RemoteForRepo(selectedRepo)
   132  	if remote == nil {
   133  		remote = r.remotes[0]
   134  		resolution = ghrepo.FullName(selectedRepo)
   135  	}
   136  
   137  	// cache the result to git config
   138  	err := git.SetRemoteResolution(remote.Name, resolution)
   139  	return selectedRepo, err
   140  }
   141  
   142  func (r *ResolvedRemotes) HeadRepos() ([]*api.Repository, error) {
   143  	if r.network == nil {
   144  		err := resolveNetwork(r)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  	}
   149  
   150  	var results []*api.Repository
   151  	for _, repo := range r.network.Repositories {
   152  		if repo != nil && repo.ViewerCanPush() {
   153  			results = append(results, repo)
   154  		}
   155  	}
   156  	return results, nil
   157  }
   158  
   159  // RemoteForRepo finds the git remote that points to a repository
   160  func (r *ResolvedRemotes) RemoteForRepo(repo ghrepo.Interface) (*Remote, error) {
   161  	for _, remote := range r.remotes {
   162  		if ghrepo.IsSame(remote, repo) {
   163  			return remote, nil
   164  		}
   165  	}
   166  	return nil, errors.New("not found")
   167  }