package setupgit import ( "fmt" "strings" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/pkg/cmd/auth/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/spf13/cobra" ) type gitConfigurator interface { Setup(hostname, username, authToken string) error } type SetupGitOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) Hostname string Force bool gitConfigure gitConfigurator } func NewCmdSetupGit(f *cmdutil.Factory, runF func(*SetupGitOptions) error) *cobra.Command { opts := &SetupGitOptions{ IO: f.IOStreams, Config: f.Config, } cmd := &cobra.Command{ Use: "setup-git", Short: "Setup git with GitHub CLI", Long: heredoc.Docf(` This command configures %[1]sgit%[1]s to use GitHub CLI as a credential helper. For more information on git credential helpers please reference: https://git-scm.com/docs/gitcredentials. By default, GitHub CLI will be set as the credential helper for all authenticated hosts. If there is no authenticated hosts the command fails with an error. Alternatively, use the %[1]s--hostname%[1]s flag to specify a single host to be configured. If the host is not authenticated with, the command fails with an error. `, "`"), Example: heredoc.Doc(` # Configure git to use GitHub CLI as the credential helper for all authenticated hosts $ gh auth setup-git # Configure git to use GitHub CLI as the credential helper for enterprise.internal host $ gh auth setup-git --hostname enterprise.internal `), RunE: func(cmd *cobra.Command, args []string) error { opts.gitConfigure = &shared.GitCredentialFlow{ Executable: f.Executable(), GitClient: f.GitClient, } if opts.Hostname == "" && opts.Force { return cmdutil.FlagErrorf("`--force` must be used in conjunction with `--hostname`") } if runF != nil { return runF(opts) } return setupGitRun(opts) }, } cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname to configure git for") cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force setup even if the host is not known. Must be used in conjunction with `--hostname`") return cmd } func setupGitRun(opts *SetupGitOptions) error { cfg, err := opts.Config() if err != nil { return err } authCfg := cfg.Authentication() hostnames := authCfg.Hosts() stderr := opts.IO.ErrOut cs := opts.IO.ColorScheme() // If a hostname was provided, we'll set up just that one if opts.Hostname != "" { if !opts.Force && !has(opts.Hostname, hostnames) { return fmt.Errorf("You are not logged into the GitHub host %q. Run %s to authenticate or provide `--force`", opts.Hostname, cs.Bold(fmt.Sprintf("gh auth login -h %s", opts.Hostname)), ) } if err := opts.gitConfigure.Setup(opts.Hostname, "", ""); err != nil { return fmt.Errorf("failed to set up git credential helper: %s", err) } return nil } // Otherwise we'll set up any known hosts if len(hostnames) == 0 { fmt.Fprintf( stderr, "You are not logged into any GitHub hosts. Run %s to authenticate.\n", cs.Bold("gh auth login"), ) return cmdutil.SilentError } for _, hostname := range hostnames { if err := opts.gitConfigure.Setup(hostname, "", ""); err != nil { return fmt.Errorf("failed to set up git credential helper: %s", err) } } return nil } func has(needle string, haystack []string) bool { for _, s := range haystack { if strings.EqualFold(s, needle) { return true } } return false }