diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index 51e74861d..ce52c55b9 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -27,10 +27,11 @@ type LoginOptions struct { Interactive bool - Hostname string - Scopes []string - Token string - Web bool + Hostname string + Scopes []string + Token string + Web bool + GitProtocol string } func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Command { @@ -54,8 +55,8 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm Alternatively, pass in a token on standard input by using %[1]s--with-token%[1]s. The minimum required scopes for the token are: "repo", "read:org". - The --scopes flag accepts a comma separated list of scopes you want your gh credentials to have. If - absent, this command ensures that gh has access to a minimum set of scopes. + The %[1]s--scopes%[1]s flag accepts a comma separated list of scopes you want your gh credentials to + have. If absent, this command ensures that gh has access to a minimum set of scopes. `, "`"), Example: heredoc.Doc(` # start interactive setup @@ -64,7 +65,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm # authenticate against github.com by reading the token from a file $ gh auth login --with-token < mytoken.txt - # authenticate with a specific GitHub Enterprise Server instance + # authenticate with a specific GitHub instance $ gh auth login --hostname enterprise.internal `), RunE: func(cmd *cobra.Command, args []string) error { @@ -111,6 +112,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm cmd.Flags().StringSliceVarP(&opts.Scopes, "scopes", "s", nil, "Additional authentication scopes for gh to have") cmd.Flags().BoolVar(&tokenStdin, "with-token", false, "Read token from standard input") cmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a browser to authenticate") + cmdutil.StringEnumFlag(cmd, &opts.GitProtocol, "git-protocol", "p", "", []string{"ssh", "https"}, "The protocol to use for git operations") return cmd } @@ -186,6 +188,7 @@ func loginRun(opts *LoginOptions) error { Web: opts.Web, Scopes: opts.Scopes, Executable: opts.MainExecutable, + GitProtocol: opts.GitProtocol, }) } diff --git a/pkg/cmd/auth/shared/login_flow.go b/pkg/cmd/auth/shared/login_flow.go index 3570106da..c33051e44 100644 --- a/pkg/cmd/auth/shared/login_flow.go +++ b/pkg/cmd/auth/shared/login_flow.go @@ -29,6 +29,7 @@ type LoginOptions struct { Web bool Scopes []string Executable string + GitProtocol string sshContext sshContext } @@ -39,8 +40,8 @@ func Login(opts *LoginOptions) error { httpClient := opts.HTTPClient cs := opts.IO.ColorScheme() - var gitProtocol string - if opts.Interactive { + gitProtocol := strings.ToLower(opts.GitProtocol) + if opts.Interactive && gitProtocol == "" { var proto string err := prompt.SurveyAskOne(&survey.Select{ Message: "What is your preferred protocol for Git operations?", diff --git a/pkg/cmdutil/flags.go b/pkg/cmdutil/flags.go index 7472fa42c..7359d8c51 100644 --- a/pkg/cmdutil/flags.go +++ b/pkg/cmdutil/flags.go @@ -1,7 +1,9 @@ package cmdutil import ( + "fmt" "strconv" + "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -21,6 +23,20 @@ func NilBoolFlag(cmd *cobra.Command, p **bool, name string, shorthand string, us return f } +// StringEnumFlag defines a new string flag that only allows values listed in options. +func StringEnumFlag(cmd *cobra.Command, p *string, name, shorthand, defaultValue string, options []string, usage string) *pflag.Flag { + val := &enumValue{string: p, options: options} + f := cmd.Flags().VarPF(val, name, shorthand, fmt.Sprintf("%s: %s", usage, formatValuesForUsageDocs(options))) + _ = cmd.RegisterFlagCompletionFunc(name, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return options, cobra.ShellCompDirectiveNoFileComp + }) + return f +} + +func formatValuesForUsageDocs(values []string) string { + return fmt.Sprintf("{%s}", strings.Join(values, "|")) +} + type stringValue struct { string **string } @@ -75,3 +91,31 @@ func (b *boolValue) Type() string { func (b *boolValue) IsBoolFlag() bool { return true } + +type enumValue struct { + string *string + options []string +} + +func (e *enumValue) Set(value string) error { + found := false + for _, opt := range e.options { + if strings.EqualFold(opt, value) { + found = true + break + } + } + if !found { + return fmt.Errorf("valid values are %s", formatValuesForUsageDocs(e.options)) + } + *e.string = value + return nil +} + +func (e *enumValue) String() string { + return *e.string +} + +func (e *enumValue) Type() string { + return "string" +}