270 lines
6.7 KiB
Go
270 lines
6.7 KiB
Go
package base
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
ctx "context"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/context"
|
|
"github.com/cli/cli/v2/git"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func explainer() string {
|
|
return heredoc.Doc(`
|
|
This command sets the default remote repository to use when querying the
|
|
GitHub API for the locally cloned repository.
|
|
|
|
gh uses the default repository for things like:
|
|
|
|
- viewing and creating pull requests
|
|
- viewing and creating issues
|
|
- viewing and creating releases
|
|
- working with GitHub Actions
|
|
|
|
### NOTE: gh does not use the default repository for managing repository and environment secrets.`)
|
|
}
|
|
|
|
type iprompter interface {
|
|
Select(string, string, []string) (int, error)
|
|
}
|
|
|
|
type SetDefaultOptions struct {
|
|
IO *iostreams.IOStreams
|
|
Remotes func() (context.Remotes, error)
|
|
HttpClient func() (*http.Client, error)
|
|
Prompter iprompter
|
|
GitClient *git.Client
|
|
|
|
Repo ghrepo.Interface
|
|
ViewMode bool
|
|
UnsetMode bool
|
|
}
|
|
|
|
func NewCmdSetDefault(f *cmdutil.Factory, runF func(*SetDefaultOptions) error) *cobra.Command {
|
|
opts := &SetDefaultOptions{
|
|
IO: f.IOStreams,
|
|
HttpClient: f.HttpClient,
|
|
Remotes: f.Remotes,
|
|
Prompter: f.Prompter,
|
|
GitClient: f.GitClient,
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "set-default [<repository>]",
|
|
Short: "Configure default repository for this directory",
|
|
Long: explainer(),
|
|
Example: heredoc.Doc(`
|
|
# Interactively select a default repository
|
|
$ gh repo set-default
|
|
|
|
# Set a repository explicitly
|
|
$ gh repo set-default owner/repo
|
|
|
|
# Set a repository using a git remote name
|
|
$ gh repo set-default origin
|
|
|
|
# View the current default repository
|
|
$ gh repo set-default --view
|
|
|
|
# Show more repository options in the interactive picker
|
|
$ git remote add newrepo https://github.com/owner/repo
|
|
$ gh repo set-default
|
|
`),
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if isLocal, err := opts.GitClient.IsLocalGitRepo(cmd.Context()); err != nil {
|
|
return err
|
|
} else if !isLocal {
|
|
return errors.New("must be run from inside a git repository")
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
var err error
|
|
opts.Repo, err = ghrepo.FromFullName(args[0])
|
|
if err != nil {
|
|
remotes, remoteErr := opts.Remotes()
|
|
if remoteErr != nil {
|
|
return remoteErr
|
|
}
|
|
|
|
remote, findErr := remotes.FindByName(args[0])
|
|
if findErr != nil {
|
|
return fmt.Errorf("given arg is not a valid repo or git remote: %w", err)
|
|
}
|
|
opts.Repo = remote.Repo
|
|
}
|
|
}
|
|
|
|
if !opts.ViewMode && !opts.IO.CanPrompt() && opts.Repo == nil {
|
|
return cmdutil.FlagErrorf("repository required when not running interactively")
|
|
}
|
|
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
|
|
return setDefaultRun(opts)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&opts.ViewMode, "view", "v", false, "View the current default repository")
|
|
cmd.Flags().BoolVarP(&opts.UnsetMode, "unset", "u", false, "Unset the current default repository")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func setDefaultRun(opts *SetDefaultOptions) error {
|
|
remotes, err := opts.Remotes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentDefaultRepo, _ := remotes.ResolvedRemote()
|
|
|
|
cs := opts.IO.ColorScheme()
|
|
|
|
if opts.ViewMode {
|
|
if currentDefaultRepo != nil {
|
|
fmt.Fprintln(opts.IO.Out, displayRemoteRepoName(currentDefaultRepo))
|
|
} else {
|
|
fmt.Fprintf(opts.IO.ErrOut,
|
|
"%s No default remote repository has been set. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
cs.FailureIcon())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if opts.UnsetMode {
|
|
var msg string
|
|
if currentDefaultRepo != nil {
|
|
if err := opts.GitClient.UnsetRemoteResolution(
|
|
ctx.Background(), currentDefaultRepo.Name); err != nil {
|
|
return err
|
|
}
|
|
msg = fmt.Sprintf("%s Unset %s as default repository",
|
|
cs.SuccessIcon(), ghrepo.FullName(currentDefaultRepo))
|
|
} else {
|
|
msg = "no default repository has been set"
|
|
}
|
|
|
|
if opts.IO.IsStdoutTTY() {
|
|
fmt.Fprintln(opts.IO.Out, msg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
apiClient := api.NewClientFromHTTP(httpClient)
|
|
|
|
resolvedRemotes, err := context.ResolveRemotesToRepos(remotes, apiClient, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
knownRepos, err := resolvedRemotes.NetworkRepos(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(knownRepos) == 0 {
|
|
return errors.New("none of the git remotes correspond to a valid remote repository")
|
|
}
|
|
|
|
var selectedRepo ghrepo.Interface
|
|
|
|
if opts.Repo != nil {
|
|
for _, knownRepo := range knownRepos {
|
|
if ghrepo.IsSame(opts.Repo, knownRepo) {
|
|
selectedRepo = opts.Repo
|
|
break
|
|
}
|
|
}
|
|
if selectedRepo == nil {
|
|
return fmt.Errorf("%s does not correspond to any git remotes", ghrepo.FullName(opts.Repo))
|
|
}
|
|
}
|
|
|
|
if selectedRepo == nil {
|
|
if len(knownRepos) == 1 {
|
|
selectedRepo = knownRepos[0]
|
|
|
|
fmt.Fprintf(opts.IO.Out, "Found only one known remote repo, %s on %s.\n",
|
|
cs.Bold(ghrepo.FullName(selectedRepo)),
|
|
cs.Bold(selectedRepo.RepoHost()))
|
|
} else {
|
|
var repoNames []string
|
|
current := ""
|
|
if currentDefaultRepo != nil {
|
|
current = ghrepo.FullName(currentDefaultRepo)
|
|
}
|
|
|
|
for _, knownRepo := range knownRepos {
|
|
repoNames = append(repoNames, ghrepo.FullName(knownRepo))
|
|
}
|
|
|
|
fmt.Fprintln(opts.IO.Out, explainer())
|
|
fmt.Fprintln(opts.IO.Out)
|
|
|
|
selected, err := opts.Prompter.Select("Which repository should be the default?", current, repoNames)
|
|
if err != nil {
|
|
return fmt.Errorf("could not prompt: %w", err)
|
|
}
|
|
selectedName := repoNames[selected]
|
|
|
|
owner, repo, _ := strings.Cut(selectedName, "/")
|
|
selectedRepo = ghrepo.New(owner, repo)
|
|
}
|
|
}
|
|
|
|
resolution := "base"
|
|
selectedRemote, _ := resolvedRemotes.RemoteForRepo(selectedRepo)
|
|
if selectedRemote == nil {
|
|
sort.Stable(remotes)
|
|
selectedRemote = remotes[0]
|
|
resolution = ghrepo.FullName(selectedRepo)
|
|
}
|
|
|
|
if currentDefaultRepo != nil {
|
|
if err := opts.GitClient.UnsetRemoteResolution(
|
|
ctx.Background(), currentDefaultRepo.Name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err = opts.GitClient.SetRemoteResolution(
|
|
ctx.Background(), selectedRemote.Name, resolution); err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.IO.IsStdoutTTY() {
|
|
cs := opts.IO.ColorScheme()
|
|
fmt.Fprintf(opts.IO.Out, "%s Set %s as the default repository for the current directory\n", cs.SuccessIcon(), ghrepo.FullName(selectedRepo))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func displayRemoteRepoName(remote *context.Remote) string {
|
|
if remote.Resolved == "" || remote.Resolved == "base" {
|
|
return ghrepo.FullName(remote)
|
|
}
|
|
|
|
repo, err := ghrepo.FromFullName(remote.Resolved)
|
|
if err != nil {
|
|
return ghrepo.FullName(remote)
|
|
}
|
|
|
|
return ghrepo.FullName(repo)
|
|
}
|