From fef41950044330365f4ce6cbdd77ad3ea406922f Mon Sep 17 00:00:00 2001 From: Benjamin Levesque <14175665+benjlevesque@users.noreply.github.com> Date: Wed, 25 Jan 2023 18:02:13 +0100 Subject: [PATCH] Add `--reviewer` flag completion (#6873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mislav Marohnić --- pkg/cmd/pr/create/create.go | 53 ++++++++++++++++++++++++++++----- pkg/cmd/pr/edit/edit.go | 18 +++++++++++ pkg/cmd/pr/shared/completion.go | 39 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 pkg/cmd/pr/shared/completion.go diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 28fa807ed..2bdf47208 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -182,6 +182,14 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co fl.Bool("no-maintainer-edit", false, "Disable maintainer's ability to modify pull request") fl.StringVar(&opts.RecoverFile, "recover", "", "Recover input from a failed run of create") + _ = cmd.RegisterFlagCompletionFunc("reviewer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + results, err := requestableReviewersForCompletion(opts) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + return results, cobra.ShellCompDirectiveNoFileComp + }) + return cmd } @@ -472,16 +480,10 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) { } client := api.NewClientFromHTTP(httpClient) - // TODO: consider obtaining remotes from GitClient instead - remotes, err := opts.Remotes() + remotes, err := getRemotes(opts) if err != nil { - // When a repo override value is given, ignore errors when fetching git remotes - // to support using this command outside of git repos. - if opts.RepoOverride == "" { - return nil, err - } + return nil, err } - repoContext, err := ghContext.ResolveRemotesToRepos(remotes, client, opts.RepoOverride) if err != nil { return nil, err @@ -628,6 +630,19 @@ func NewCreateContext(opts *CreateOptions) (*CreateContext, error) { } +func getRemotes(opts *CreateOptions) (ghContext.Remotes, error) { + // TODO: consider obtaining remotes from GitClient instead + remotes, err := opts.Remotes() + if err != nil { + // When a repo override value is given, ignore errors when fetching git remotes + // to support using this command outside of git repos. + if opts.RepoOverride == "" { + return nil, err + } + } + return remotes, nil +} + func submitPR(opts CreateOptions, ctx CreateContext, state shared.IssueMetadataState) error { client := ctx.Client @@ -782,4 +797,26 @@ func humanize(s string) string { return strings.Map(h, s) } +func requestableReviewersForCompletion(opts *CreateOptions) ([]string, error) { + httpClient, err := opts.HttpClient() + if err != nil { + return nil, err + } + + remotes, err := getRemotes(opts) + if err != nil { + return nil, err + } + repoContext, err := ghContext.ResolveRemotesToRepos(remotes, api.NewClientFromHTTP(httpClient), opts.RepoOverride) + if err != nil { + return nil, err + } + baseRepo, err := repoContext.BaseRepo(opts.IO) + if err != nil { + return nil, err + } + + return shared.RequestableReviewersForCompletion(httpClient, baseRepo) +} + var gitPushRegexp = regexp.MustCompile("^remote: (Create a pull request.*by visiting|[[:space:]]*https://.*/pull/new/).*\n?$") diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index d4a9a32b5..216d0d6d2 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -148,6 +148,24 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the pull request from projects by `name`") cmd.Flags().StringVarP(&opts.Editable.Milestone.Value, "milestone", "m", "", "Edit the milestone the pull request belongs to by `name`") + for _, flagName := range []string{"add-reviewer", "remove-reviewer"} { + _ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + baseRepo, err := f.BaseRepo() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + httpClient, err := f.HttpClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + results, err := shared.RequestableReviewersForCompletion(httpClient, baseRepo) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + return results, cobra.ShellCompDirectiveNoFileComp + }) + } + return cmd } diff --git a/pkg/cmd/pr/shared/completion.go b/pkg/cmd/pr/shared/completion.go new file mode 100644 index 000000000..e07abc5a7 --- /dev/null +++ b/pkg/cmd/pr/shared/completion.go @@ -0,0 +1,39 @@ +package shared + +import ( + "fmt" + "net/http" + "sort" + "strings" + "time" + + "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/ghrepo" +) + +func RequestableReviewersForCompletion(httpClient *http.Client, repo ghrepo.Interface) ([]string, error) { + client := api.NewClientFromHTTP(api.NewCachedHTTPClient(httpClient, time.Minute*2)) + + metadata, err := api.RepoMetadata(client, repo, api.RepoMetadataInput{Reviewers: true}) + if err != nil { + return nil, err + } + + results := []string{} + for _, user := range metadata.AssignableUsers { + if strings.EqualFold(user.Login, metadata.CurrentLogin) { + continue + } + if user.Name != "" { + results = append(results, fmt.Sprintf("%s\t%s", user.Login, user.Name)) + } else { + results = append(results, user.Login) + } + } + for _, team := range metadata.Teams { + results = append(results, fmt.Sprintf("%s/%s", repo.RepoOwner(), team.Slug)) + } + + sort.Strings(results) + return results, nil +}