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 }