diff --git a/completers/gh_completer/cmd/auth_login.go b/completers/gh_completer/cmd/auth_login.go index 943991ea88..4bf7d83b85 100644 --- a/completers/gh_completer/cmd/auth_login.go +++ b/completers/gh_completer/cmd/auth_login.go @@ -9,14 +9,14 @@ import ( var auth_loginCmd = &cobra.Command{ Use: "login", - Short: "Authenticate with a GitHub host", + Short: "Log in to a GitHub account", Run: func(cmd *cobra.Command, args []string) {}, } func init() { carapace.Gen(auth_loginCmd).Standalone() - auth_loginCmd.Flags().StringP("git-protocol", "p", "", "The protocol to use for git operations: {ssh|https}") + auth_loginCmd.Flags().StringP("git-protocol", "p", "", "The protocol to use for git operations on this host: {ssh|https}") auth_loginCmd.Flags().StringP("hostname", "h", "", "The hostname of the GitHub instance to authenticate with") auth_loginCmd.Flags().Bool("insecure-storage", false, "Save authentication credentials in plain text instead of credential store") auth_loginCmd.Flags().StringSliceP("scopes", "s", []string{}, "Additional authentication scopes to request") diff --git a/completers/gh_completer/cmd/auth_logout.go b/completers/gh_completer/cmd/auth_logout.go index 625c0e03b5..bde36c53ab 100644 --- a/completers/gh_completer/cmd/auth_logout.go +++ b/completers/gh_completer/cmd/auth_logout.go @@ -3,12 +3,13 @@ package cmd import ( "github.com/rsteube/carapace" "github.com/rsteube/carapace-bin/completers/gh_completer/cmd/action" + "github.com/rsteube/carapace-bin/pkg/actions/tools/gh" "github.com/spf13/cobra" ) var auth_logoutCmd = &cobra.Command{ Use: "logout", - Short: "Log out of a GitHub host", + Short: "Log out of a GitHub account", Run: func(cmd *cobra.Command, args []string) {}, } @@ -16,9 +17,13 @@ func init() { carapace.Gen(auth_logoutCmd).Standalone() auth_logoutCmd.Flags().StringP("hostname", "h", "", "The hostname of the GitHub instance to log out of") + auth_logoutCmd.Flags().StringP("user", "u", "", "The account to log out of") authCmd.AddCommand(auth_logoutCmd) carapace.Gen(auth_logoutCmd).FlagCompletion(carapace.ActionMap{ "hostname": action.ActionConfigHosts(), + "user": carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return gh.ActionConfigUsers(auth_logoutCmd.Flag("hostname").Value.String()) + }), }) } diff --git a/completers/gh_completer/cmd/auth_switch.go b/completers/gh_completer/cmd/auth_switch.go new file mode 100644 index 0000000000..8f71bc348f --- /dev/null +++ b/completers/gh_completer/cmd/auth_switch.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "github.com/rsteube/carapace" + "github.com/rsteube/carapace-bin/completers/gh_completer/cmd/action" + "github.com/rsteube/carapace-bin/pkg/actions/tools/gh" + "github.com/spf13/cobra" +) + +var auth_switchCmd = &cobra.Command{ + Use: "switch", + Short: "Switch active GitHub account", + Run: func(cmd *cobra.Command, args []string) {}, +} + +func init() { + carapace.Gen(auth_switchCmd).Standalone() + + auth_switchCmd.Flags().StringP("hostname", "h", "", "The hostname of the GitHub instance to switch account for") + auth_switchCmd.Flags().StringP("user", "u", "", "The account to switch to") + authCmd.AddCommand(auth_switchCmd) + + carapace.Gen(auth_switchCmd).FlagCompletion(carapace.ActionMap{ + "hostname": action.ActionConfigHosts(), + "user": carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return gh.ActionConfigUsers(auth_switchCmd.Flag("hostname").Value.String()) + }), + }) +} diff --git a/completers/gh_completer/cmd/auth_token.go b/completers/gh_completer/cmd/auth_token.go index c6ef96e132..560f3a7ce3 100644 --- a/completers/gh_completer/cmd/auth_token.go +++ b/completers/gh_completer/cmd/auth_token.go @@ -3,12 +3,13 @@ package cmd import ( "github.com/rsteube/carapace" "github.com/rsteube/carapace-bin/completers/gh_completer/cmd/action" + "github.com/rsteube/carapace-bin/pkg/actions/tools/gh" "github.com/spf13/cobra" ) var auth_tokenCmd = &cobra.Command{ Use: "token", - Short: "Print the auth token gh is configured to use", + Short: "Print the authentication token gh uses for a hostname and account", Run: func(cmd *cobra.Command, args []string) {}, } @@ -17,10 +18,14 @@ func init() { auth_tokenCmd.Flags().StringP("hostname", "h", "", "The hostname of the GitHub instance authenticated with") auth_tokenCmd.Flags().Bool("secure-storage", false, "Search only secure credential store for authentication token") + auth_tokenCmd.Flags().StringP("user", "u", "", "The account to log out of") auth_tokenCmd.Flag("secure-storage").Hidden = true authCmd.AddCommand(auth_tokenCmd) carapace.Gen(auth_tokenCmd).FlagCompletion(carapace.ActionMap{ "hostname": action.ActionConfigHosts(), + "user": carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return gh.ActionConfigUsers(auth_tokenCmd.Flag("hostname").Value.String()) + }), }) } diff --git a/completers/gh_completer/cmd/codespace_create.go b/completers/gh_completer/cmd/codespace_create.go index d5f840c4b1..b8791d0b09 100644 --- a/completers/gh_completer/cmd/codespace_create.go +++ b/completers/gh_completer/cmd/codespace_create.go @@ -21,7 +21,7 @@ func init() { codespace_createCmd.Flags().StringP("branch", "b", "", "repository branch") codespace_createCmd.Flags().Bool("default-permissions", false, "do not prompt to accept additional permissions requested by the codespace") codespace_createCmd.Flags().String("devcontainer-path", "", "path to the devcontainer.json file to use when creating codespace") - codespace_createCmd.Flags().StringP("display-name", "d", "", "display name for the codespace") + codespace_createCmd.Flags().StringP("display-name", "d", "", "display name for the codespace (48 characters or less)") codespace_createCmd.Flags().String("idle-timeout", "", "allowed inactivity before codespace is stopped, e.g. \"10m\", \"1h\"") codespace_createCmd.Flags().StringP("location", "l", "", "location: {EastUs|SouthEastAsia|WestEurope|WestUs2} (determined automatically if not provided)") codespace_createCmd.Flags().StringP("machine", "m", "", "hardware specifications for the VM") diff --git a/completers/gh_completer/cmd/repo_sync.go b/completers/gh_completer/cmd/repo_sync.go index 9b989e25fa..45fb3b1efa 100644 --- a/completers/gh_completer/cmd/repo_sync.go +++ b/completers/gh_completer/cmd/repo_sync.go @@ -16,7 +16,7 @@ var repo_syncCmd = &cobra.Command{ func init() { carapace.Gen(repo_syncCmd).Standalone() - repo_syncCmd.Flags().StringP("branch", "b", "", "Branch to sync (default: main branch)") + repo_syncCmd.Flags().StringP("branch", "b", "", "Branch to sync (default: default branch)") repo_syncCmd.Flags().Bool("force", false, "Hard reset the branch of the destination repository to match the source repository") repo_syncCmd.Flags().StringP("source", "s", "", "Source repository") repoCmd.AddCommand(repo_syncCmd) diff --git a/completers/gh_completer/cmd/run_list.go b/completers/gh_completer/cmd/run_list.go index 8afac19709..366805e5b4 100644 --- a/completers/gh_completer/cmd/run_list.go +++ b/completers/gh_completer/cmd/run_list.go @@ -19,6 +19,7 @@ func init() { carapace.Gen(run_listCmd).Standalone() run_listCmd.Flags().StringP("branch", "b", "", "Filter runs by branch") + run_listCmd.Flags().StringP("commit", "c", "", "Filter runs by the `SHA` of the commit") run_listCmd.Flags().String("created", "", "Filter runs by the `date` it was created") run_listCmd.Flags().StringP("event", "e", "", "Filter runs by which `event` triggered the run") run_listCmd.Flags().StringP("jq", "q", "", "Filter JSON output using a jq `expression`") @@ -31,7 +32,10 @@ func init() { runCmd.AddCommand(run_listCmd) carapace.Gen(run_listCmd).FlagCompletion(carapace.ActionMap{ - "branch": action.ActionBranches(run_listCmd), + "branch": action.ActionBranches(run_listCmd), + "commit": carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return action.ActionBranchCommits(run_listCmd, run_listCmd.Flag("branch").Value.String()) + }), "created": time.ActionDate(), "event": gh.ActionWorkflowEvents(), "json": action.ActionRunFields(), diff --git a/completers/gh_completer/cmd/workflow_run.go b/completers/gh_completer/cmd/workflow_run.go index 413683d733..c591238c7e 100644 --- a/completers/gh_completer/cmd/workflow_run.go +++ b/completers/gh_completer/cmd/workflow_run.go @@ -15,7 +15,7 @@ var workflow_runCmd = &cobra.Command{ func init() { carapace.Gen(workflow_runCmd).Standalone() - workflow_runCmd.Flags().StringSliceP("field", "F", []string{}, "Add a string parameter in `key=value` format, respecting @ syntax") + workflow_runCmd.Flags().StringSliceP("field", "F", []string{}, "Add a string parameter in `key=value` format, respecting @ syntax (see \"gh help api\").") workflow_runCmd.Flags().Bool("json", false, "Read workflow inputs as JSON via STDIN") workflow_runCmd.Flags().StringSliceP("raw-field", "f", []string{}, "Add a string parameter in `key=value` format") workflow_runCmd.Flags().StringP("ref", "r", "", "The branch or tag name which contains the version of the workflow file you'd like to run") diff --git a/pkg/actions/tools/gh/config.go b/pkg/actions/tools/gh/config.go index cb8c457226..abef5d5df5 100644 --- a/pkg/actions/tools/gh/config.go +++ b/pkg/actions/tools/gh/config.go @@ -2,36 +2,43 @@ package gh import ( "os" + "path/filepath" "github.com/rsteube/carapace" - "github.com/rsteube/carapace-bin/pkg/actions/tools/gh/config" - "github.com/rsteube/carapace/pkg/xdg" + "github.com/rsteube/carapace/pkg/traverse" "gopkg.in/yaml.v3" ) -func userFor(host string) (string, error) { - if host == "" { - host = "github.com" - } - - configDir, err := xdg.UserConfigDir() - if err != nil { - return "", err - } +type hostConfig map[string]struct { + User string `yaml:"user"` + OauthToken string `yaml:"oauth_token"` + GitProtocol string `yaml:"git_protocol"` + Users map[string]struct { + OauthToken string `yaml:"oauth_token"` + } `yaml:"users"` +} - content, err := os.ReadFile(configDir + "/gh/hosts.yml") - if err != nil { - return "", err +func configDir(c carapace.Context) string { + if v := c.Getenv("GH_CONFIG_DIR"); v != "" { + return v } + homeDir, _ := traverse.UserHomeDir(c) // TODO handle error + return filepath.Join(homeDir, ".config", "gh") +} - var hosts map[string]struct { - User string - } - if err := yaml.Unmarshal(content, &hosts); err != nil { - return "", err - } +func actionHostConfig(f func(config hostConfig) carapace.Action) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + content, err := os.ReadFile(configDir(c) + "/hosts.yml") + if err != nil { + return carapace.ActionMessage(err.Error()) + } - return hosts[host].User, nil + var config hostConfig + if err := yaml.Unmarshal(content, &config); err != nil { + return carapace.ActionMessage(err.Error()) + } + return f(config) + }) } // ActionConfigHosts completes configured hosts @@ -39,15 +46,30 @@ func userFor(host string) (string, error) { // github.com // another.com func ActionConfigHosts() carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if config, err := config.ParseDefaultConfig(); err != nil { - return carapace.ActionMessage("failed to parse DefaultConfig: " + err.Error()) - } else { - if hosts, err := config.Hosts(); err != nil { - return carapace.ActionMessage("failed ot loadd hosts: " + err.Error()) - } else { - return carapace.ActionValues(hosts...) + return actionHostConfig(func(config hostConfig) carapace.Action { + vals := make([]string, 0) + for host := range config { + vals = append(vals, host) + } + return carapace.ActionValues(vals...) + }) +} + +// ActionConfigUsers completes configured users +// +// userA +// userB +func ActionConfigUsers(host string) carapace.Action { + return actionHostConfig(func(config hostConfig) carapace.Action { + vals := make([]string, 0) + for confHost, conf := range config { + switch host { + case "", confHost: + for user := range conf.Users { + vals = append(vals, user) + } } } + return carapace.ActionValues(vals...) }) } diff --git a/pkg/actions/tools/gh/graphql.go b/pkg/actions/tools/gh/graphql.go index 24f13e3d63..ad0ac024a0 100644 --- a/pkg/actions/tools/gh/graphql.go +++ b/pkg/actions/tools/gh/graphql.go @@ -9,7 +9,7 @@ import ( ) func graphQlAction(opts RepoOpts, query string, v interface{}, transform func() carapace.Action) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + return actionHostConfig(func(config hostConfig) carapace.Action { params := make([]string, 0) if strings.Contains(query, "$owner") { params = append(params, "$owner: String!") @@ -27,10 +27,11 @@ func graphQlAction(opts RepoOpts, query string, v interface{}, transform func() } if opts.Owner == "@me" { - var err error - if opts.Owner, err = userFor(opts.Host); err != nil { - return carapace.ActionMessage(err.Error()) + conf, ok := config[opts.Host] + if !ok { + return carapace.ActionMessage("unknown host") } + opts.Owner = conf.User } return carapace.ActionExecCommand("gh", "api", "--hostname", opts.Host, "--header", "Accept: application/vnd.github.merge-info-preview+json", "graphql", "-F", "owner="+opts.Owner, "-F", "repo="+opts.Name, "-f", fmt.Sprintf("query=query%v {%v}", queryParams, query))(func(output []byte) carapace.Action {