-
Couldn't load subscription status.
- Fork 7.3k
feat: let user select pr to checkout #9868
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Improve the interactive PR selection UI by - prefix the PR number with hashcode # - perserve the text formatting (bold) upon an option is hovered - add the PR head label Technical changes: - Replace \033[0m with \033[39m for maintaining text formatting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 Hey @nilvng ✨
Thank you for taking on this old feature request. I am personally looking forward to this feature 😁 ❤️
I added a few comments on the code for things that I think we need to address. I think the team and I may have more comments later, but this should get us moving 😃
I've also noted a few things generally below...
I think we need to address the inverse of the acceptance criteria; prompting should only happen when we are attached to a TTY.
Here is the current behavior when run non-interactively:
We must not prompt in this case. Here is an example from gh workflow run showing the desired behavior:
Here is the implementation that we can pull inspiration from:
cli/pkg/cmd/workflow/run/run.go
Lines 98 to 99 in 9c55099
| } else if !opts.IO.CanPrompt() { | |
| return cmdutil.FlagErrorf("workflow ID, name, or filename required when not running interactively") |
There are also some unit test failures that need to be investigated, though I suspect it may be due to some conflict with the trunk branch changes I merged back into your branch 🤔
|
@nilvng Thanks for your continued work on this and insights into your thought process ✨ I've been reviewing this a bit deeper and have a few more things I'd like to address, and I also have a proposal for a refactor. Interactive checkouts can behave unexpectedly for fork PRsTLDR: we must include both While testing this, I've noticed that some PRs cause my terminal prompt to behave unexpectedly, and clue me into that the interactive flow was doing different This is what I'd typically expect instead: I discovered this was because of this logic which occurs later in the process: cli/pkg/cmd/pr/checkout/checkout.go Lines 271 to 275 in d92e529
Since the interactive flow was not requesting these fields ( Interactive flow cli/pkg/cmd/pr/checkout/checkout.go Lines 321 to 330 in d92e529
non-interactive flow cli/pkg/cmd/pr/checkout/checkout.go Lines 98 to 108 in d92e529
Refactoring interactive flowI'd like to propose a refactor to the logic around deciding whether to prompt. I'd like to:
Note: this proposal includes solutions to the above problem regarding the I'd like to push a commit to this PR with my proposal below, if you agree with the essence of the refactor, then we can refactor further if needed ❤️ Please feel free to Edit: here's an example from Show diff
diff --git a/pkg/cmd/pr/checkout/checkout.go b/pkg/cmd/pr/checkout/checkout.go
index e78eb026..d850f70c 100644
--- a/pkg/cmd/pr/checkout/checkout.go
+++ b/pkg/cmd/pr/checkout/checkout.go
@@ -69,11 +69,14 @@ func NewCmdCheckout(f *cmdutil.Factory, runF func(*CheckoutOptions) error) *cobr
if len(args) > 0 {
opts.SelectorArg = args[0]
+ } else if !opts.IO.CanPrompt() {
+ return cmdutil.FlagErrorf("pull request number, URL, or branch required when not running interactively")
}
if runF != nil {
return runF(opts)
}
+
return checkoutRun(opts)
},
}
@@ -87,47 +90,21 @@ func NewCmdCheckout(f *cmdutil.Factory, runF func(*CheckoutOptions) error) *cobr
}
func checkoutRun(opts *CheckoutOptions) error {
- var (
- baseRepo ghrepo.Interface
- pr *api.PullRequest
- err error
- )
-
- switch {
- case opts.SelectorArg != "":
- pr, baseRepo, err = opts.Finder.Find(shared.FindOptions{
- Selector: opts.SelectorArg,
- Fields: []string{
- "number",
- "headRefName",
- "headRepository",
- "headRepositoryOwner",
- "isCrossRepository",
- "maintainerCanModify",
- },
- })
- if err != nil {
- return err
- }
-
- default:
- if !opts.IO.CanPrompt() {
- return cmdutil.FlagErrorf("must provide a pull request number (or URL or branch) when not running interactively")
- }
-
- httpClient, err := opts.HttpClient()
- if err != nil {
- return err
- }
+ httpClient, err := opts.HttpClient()
+ if err != nil {
+ return err
+ }
- if baseRepo, err = opts.BaseRepo(); err != nil {
- return err
- }
+ baseRepo, err := opts.BaseRepo()
+ if err != nil {
+ return err
+ }
- if pr, err = selectPR(httpClient, baseRepo, opts.Prompter, opts.IO.ColorScheme()); err != nil {
- return err
- }
+ pr, err := resolvePR(httpClient, baseRepo, opts.Prompter, opts.SelectorArg, opts.Finder, opts.IO.ColorScheme())
+ if err != nil {
+ return err
}
+
cfg, err := opts.Config()
if err != nil {
return err
@@ -317,22 +294,46 @@ func executeCmds(client *git.Client, credentialPattern git.CredentialPattern, cm
return nil
}
-func selectPR(httpClient *http.Client, baseRepo ghrepo.Interface, prompter shared.Prompter, cs *iostreams.ColorScheme) (*api.PullRequest, error) {
+func resolvePR(httpClient *http.Client, baseRepo ghrepo.Interface, prompter shared.Prompter, pullRequestSelector string, pullRequestFinder shared.PRFinder, cs *iostreams.ColorScheme) (*api.PullRequest, error) {
+ // When non-interactive
+ if pullRequestSelector != "" {
+ pr, _, err := pullRequestFinder.Find(shared.FindOptions{
+ Selector: pullRequestSelector,
+ Fields: []string{
+ "number",
+ "headRefName",
+ "headRepository",
+ "headRepositoryOwner",
+ "isCrossRepository",
+ "maintainerCanModify",
+ },
+ })
+ if err != nil {
+ return nil, err
+ }
+ return pr, nil
+ }
+
+ // When interactive
listResult, err := list.ListPullRequests(httpClient, baseRepo, shared.FilterOptions{Entity: "pr", State: "open", Fields: []string{
"number",
"title",
"state",
"url",
"headRefName",
+ "headRepository",
"headRepositoryOwner",
"isCrossRepository",
"isDraft",
"createdAt",
+ "maintainerCanModify",
}}, 10)
if err != nil {
return nil, err
}
+
pr, err := promptForPR(prompter, cs, *listResult)
+
return pr, err
}
|
68ee973 to
f1d681e
Compare
Co-authored-by: Kynan Ware <[email protected]>
|
Hey @BagToad How are you doin!
Show diffdiff --git a/pkg/cmd/pr/checkout/checkout.go b/pkg/cmd/pr/checkout/checkout.go
index 9516e580..b9ded029 100644
--- a/pkg/cmd/pr/checkout/checkout.go
+++ b/pkg/cmd/pr/checkout/checkout.go
@@ -92,47 +92,21 @@ func NewCmdCheckout(f *cmdutil.Factory, runF func(*CheckoutOptions) error) *cobr
}
func checkoutRun(opts *CheckoutOptions) error {
- var (
- baseRepo ghrepo.Interface
- pr *api.PullRequest
- err error
- )
-
- switch {
- case opts.SelectorArg != "":
- pr, baseRepo, err = opts.Finder.Find(shared.FindOptions{
- Selector: opts.SelectorArg,
- Fields: []string{
- "number",
- "headRefName",
- "headRepository",
- "headRepositoryOwner",
- "isCrossRepository",
- "maintainerCanModify",
- },
- })
- if err != nil {
- return err
- }
-
- default:
- if !opts.Interactive {
- return cmdutil.FlagErrorf("must provide a pull request number (or URL or branch) when not running interactively")
- }
-
- httpClient, err := opts.HttpClient()
- if err != nil {
- return err
- }
+ baseRepo, err := opts.BaseRepo()
+ if err != nil {
+ return err
+ }
- if baseRepo, err = opts.BaseRepo(); err != nil {
- return err
- }
+ client, err := opts.HttpClient()
+ if err != nil {
+ return err
+ }
- if pr, err = selectPR(httpClient, baseRepo, opts.Prompter, opts.IO.ColorScheme()); err != nil {
- return err
- }
+ pr, err := resolvePR(client, baseRepo, opts.Prompter, opts.SelectorArg, opts.Interactive, opts.Finder, opts.IO.ColorScheme())
+ if err != nil {
+ return err
}
+
cfg, err := opts.Config()
if err != nil {
return err
@@ -322,22 +296,49 @@ func executeCmds(client *git.Client, credentialPattern git.CredentialPattern, cm
return nil
}
-func selectPR(httpClient *http.Client, baseRepo ghrepo.Interface, prompter shared.Prompter, cs *iostreams.ColorScheme) (*api.PullRequest, error) {
+func resolvePR(httpClient *http.Client, baseRepo ghrepo.Interface, prompter shared.Prompter, pullRequestSelector string, isInteractive bool, pullRequestFinder shared.PRFinder, cs *iostreams.ColorScheme) (*api.PullRequest, error) {
+ // When non-interactive
+ if pullRequestSelector != "" {
+ pr, _, err := pullRequestFinder.Find(shared.FindOptions{
+ Selector: pullRequestSelector,
+ Fields: []string{
+ "number",
+ "headRefName",
+ "headRepository",
+ "headRepositoryOwner",
+ "isCrossRepository",
+ "maintainerCanModify",
+ },
+ })
+ if err != nil {
+ return nil, err
+ }
+ return pr, nil
+ }
+ if !isInteractive {
+ return nil, cmdutil.FlagErrorf("pull request number, URL, or branch required when not running interactively")
+ }
+ // When interactive
listResult, err := list.ListPullRequests(httpClient, baseRepo, shared.FilterOptions{Entity: "pr", State: "open", Fields: []string{
"number",
"title",
"state",
"url",
+ "isDraft",
+ "createdAt",
+
"headRefName",
+ "headRepository",
"headRepositoryOwner",
"isCrossRepository",
- "isDraft",
- "createdAt",
+ "maintainerCanModify",
}}, 10)
if err != nil {
return nil, err
}
+
pr, err := promptForPR(prompter, cs, *listResult)
+
return pr, err
}
diff --git a/pkg/cmd/pr/checkout/checkout_test.go b/pkg/cmd/pr/checkout/checkout_test.go
index 49ac0bb2..5c2109f2 100644
--- a/pkg/cmd/pr/checkout/checkout_test.go
+++ b/pkg/cmd/pr/checkout/checkout_test.go
@@ -85,6 +85,10 @@ func Test_checkoutRun(t *testing.T) {
finder := shared.NewMockFinder("123", pr, baseRepo)
return finder
}(),
+ BaseRepo: func() (ghrepo.Interface, error) {
+ baseRepo, _ := stubPR("OWNER/REPO:master", "OWNER/REPO:feature")
+ return baseRepo, nil
+ },
Config: func() (gh.Config, error) {
return config.NewBlankConfig(), nil
},
@@ -113,6 +117,10 @@ func Test_checkoutRun(t *testing.T) {
finder := shared.NewMockFinder("123", pr, baseRepo)
return finder
}(),
+ BaseRepo: func() (ghrepo.Interface, error) {
+ baseRepo, _ := stubPR("OWNER/REPO:master", "OWNER/REPO:feature")
+ return baseRepo, nil
+ },
Config: func() (gh.Config, error) {
return config.NewBlankConfig(), nil
},
@@ -143,6 +151,10 @@ func Test_checkoutRun(t *testing.T) {
finder := shared.NewMockFinder("123", pr, baseRepo)
return finder
}(),
+ BaseRepo: func() (ghrepo.Interface, error) {
+ baseRepo, _ := stubPR("OWNER/REPO:master", "OWNER/REPO:feature")
+ return baseRepo, nil
+ },
Config: func() (gh.Config, error) {
return config.NewBlankConfig(), nil
},
@@ -171,6 +183,10 @@ func Test_checkoutRun(t *testing.T) {
finder := shared.NewMockFinder("123", pr, baseRepo)
return finder
}(),
+ BaseRepo: func() (ghrepo.Interface, error) {
+ baseRepo, _ := stubPR("OWNER/REPO:master", "hubot/REPO:feature")
+ return baseRepo, nil
+ },
Config: func() (gh.Config, error) {
return config.NewBlankConfig(), nil
},
@@ -196,6 +212,9 @@ func Test_checkoutRun(t *testing.T) {
opts: &CheckoutOptions{
SelectorArg: "",
Interactive: false,
+ BaseRepo: func() (ghrepo.Interface, error) {
+ return ghrepo.New("OWNER", "REPO"), nil
+ },
},
remotes: map[string]string{
"origin": "OWNER/REPO",
@@ -303,7 +322,7 @@ func Test_checkoutRun(t *testing.T) {
/** LEGACY TESTS **/
-func runCommand(rt http.RoundTripper, remotes context.Remotes, branch string, cli string) (*test.CmdOut, error) {
+func runCommand(rt http.RoundTripper, remotes context.Remotes, branch string, cli string, baseRepo ghrepo.Interface) (*test.CmdOut, error) {
ios, _, stdout, stderr := iostreams.Test()
factory := &cmdutil.Factory{
@@ -332,6 +351,9 @@ func runCommand(rt http.RoundTripper, remotes context.Remotes, branch string, cl
GhPath: "some/path/gh",
GitPath: "some/path/git",
},
+ BaseRepo: func() (ghrepo.Interface, error) {
+ return baseRepo, nil
+ },
}
cmd := NewCmdCheckout(factory, nil)
@@ -369,7 +391,7 @@ func TestPRCheckout_sameRepo(t *testing.T) {
cs.Register(`git show-ref --verify -- refs/heads/feature`, 1, "")
cs.Register(`git checkout -b feature --track origin/feature`, 0, "")
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -391,7 +413,7 @@ func TestPRCheckout_existingBranch(t *testing.T) {
cs.Register(`git checkout feature`, 0, "")
cs.Register(`git merge --ff-only refs/remotes/origin/feature`, 0, "")
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -424,7 +446,7 @@ func TestPRCheckout_differentRepo_remoteExists(t *testing.T) {
cs.Register(`git show-ref --verify -- refs/heads/feature`, 1, "")
cs.Register(`git checkout -b feature --track robot-fork/feature`, 0, "")
- output, err := runCommand(http, remotes, "master", `123`)
+ output, err := runCommand(http, remotes, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -449,7 +471,7 @@ func TestPRCheckout_differentRepo(t *testing.T) {
cs.Register(`git config branch\.feature\.pushRemote origin`, 0, "")
cs.Register(`git config branch\.feature\.merge refs/pull/123/head`, 0, "")
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -470,7 +492,7 @@ func TestPRCheckout_differentRepo_existingBranch(t *testing.T) {
cs.Register(`git config branch\.feature\.merge`, 0, "refs/heads/feature\n")
cs.Register(`git checkout feature`, 0, "")
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -491,7 +513,7 @@ func TestPRCheckout_detachedHead(t *testing.T) {
cs.Register(`git config branch\.feature\.merge`, 0, "refs/heads/feature\n")
cs.Register(`git checkout feature`, 0, "")
- output, err := runCommand(http, nil, "", `123`)
+ output, err := runCommand(http, nil, "", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -512,7 +534,7 @@ func TestPRCheckout_differentRepo_currentBranch(t *testing.T) {
cs.Register(`git config branch\.feature\.merge`, 0, "refs/heads/feature\n")
cs.Register(`git merge --ff-only FETCH_HEAD`, 0, "")
- output, err := runCommand(http, nil, "feature", `123`)
+ output, err := runCommand(http, nil, "feature", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -528,7 +550,7 @@ func TestPRCheckout_differentRepo_invalidBranchName(t *testing.T) {
_, cmdTeardown := run.Stub()
defer cmdTeardown(t)
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.EqualError(t, err, `invalid branch name: "-foo"`)
assert.Equal(t, "", output.Stderr())
assert.Equal(t, "", output.Stderr())
@@ -553,7 +575,7 @@ func TestPRCheckout_maintainerCanModify(t *testing.T) {
cs.Register(`git config branch\.feature\.pushRemote https://github\.com/hubot/REPO\.git`, 0, "")
cs.Register(`git config branch\.feature\.merge refs/heads/feature`, 0, "")
- output, err := runCommand(http, nil, "master", `123`)
+ output, err := runCommand(http, nil, "master", `123`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -576,7 +598,7 @@ func TestPRCheckout_recurseSubmodules(t *testing.T) {
cs.Register(`git submodule sync --recursive`, 0, "")
cs.Register(`git submodule update --init --recursive`, 0, "")
- output, err := runCommand(http, nil, "master", `123 --recurse-submodules`)
+ output, err := runCommand(http, nil, "master", `123 --recurse-submodules`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr())
@@ -597,7 +619,7 @@ func TestPRCheckout_force(t *testing.T) {
cs.Register(`git checkout feature`, 0, "")
cs.Register(`git reset --hard refs/remotes/origin/feature`, 0, "")
- output, err := runCommand(http, nil, "master", `123 --force`)
+ output, err := runCommand(http, nil, "master", `123 --force`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
@@ -618,7 +640,7 @@ func TestPRCheckout_detach(t *testing.T) {
cs.Register(`git remote get-url origin`, 0, "https://github.com/hubot/REPO.git")
cs.Register(`git fetch origin refs/pull/123/head`, 0, "")
- output, err := runCommand(http, nil, "", `123 --detach`)
+ output, err := runCommand(http, nil, "", `123 --detach`, baseRepo)
assert.NoError(t, err)
assert.Equal(t, "", output.String())
assert.Equal(t, "", output.Stderr()) |
|
hey @BagToad cc @williammartin, The core functionality of the PR is now stable 🚀 |
|
👋 Hey @nilvng - thanks for your continued work and collaboration on this PR ✨ Thank you for updating the color as we discussed and working on the unit tests. Also wanted to mention that adding the state to the prompt seems like a good idea to me 😁 I'm going to try and address a few things that should hopefully unblock some further development of this feature pending a larger team review, but let me know if I missed anything that you need from us ❤️
I'm not sure about this one 🤔 I think I'd like to hear the rest of the team's thoughts on this and legacy checkout tests once they have some time.
Sounds good! I thought a lot about this, and I'm not sure if I know the best solution - we might need to iterate on it. I think we may want to start with following how If I could soundboard my thoughts on what we are testing, I think our testing goals are...
It leaves a bit to be desired, but let's start there and perhaps the rest of the team will have more feedback as we go. I hope this unblocks you to work on that refactor when desired 😁
I do have some things I've been thinking about... 🤔 💭 We should consider a "zero state" messageCurrent Desired (example from Note More inconsistency here as the "zero state" for Nonetheless, I think if we're doing this right, I think we should have a message for when there are no PRs found. 💭 Listing only 10 PRs feels like it might not be enoughNote I don't think any of this blocks this PR. In fact, I'm not convinced this feedback needs to be addressed in this PR. Just noting my concerns for feedback. I don't know what the right solution is for this, but I wanted to express how this might not be the optimal prompting experience for some folks (like our favorite repo, the Just brainstorming some things here: Allow a Problems: this isn't a precedent that I could find; I cannot find other commands allowing the number of items prompted to be adjusted via a flag like this. Something that concerns me is the UX of having a flag that only does stuff for the prompting. I think this means that if that's something the team is okay with, we'd want to be very clear with the flag naming. Don't list draft PRs in the prompt by default, maybe permit listing drafts with a flag Problems: Same problems as above regarding flags adjusting the content of only the prompt list. Personal opinion - generally, the PRs I want to checkout are going to be open with some exceptions that I would be happy to pass a flag for. Maybe this behavior could be seen as cumbersome for some that work with drafts often and would dislike that default behavior. Edit: It's worth mentioning that the reason I bring this up is because excluding drafts would help me squeeze more value out of those 10 PRs that I get to see Create an environment variable to adjust the number of prompt items for commands that support it including commands like Problems: Perhaps discoverability of an environment variable leaves some to be desired, but I don't think that's a reason to avoid this option. Worth mentioning is that we do support env vars like |
|
@williammartin Thanks for checking in! I'd really appreciate your feedback on the PR. I think it's in a good state for review, though I'm still working through a couple of open questions. For context, here's what I've been working on recently:
There are also things still are quite up in the air:
|
|
👋 Hey @nilvng, thank you for your patience on this. We're getting caught up after the holidays 😁
Great work! ✨ I discussed this PR and your questions with @williammartin:
Neither of us see much value in moving it to an
@williammartin and I both agreed that, while there may be improvement opportunities here, it shouldn't block this PR from being merged. So, we'll "ship to learn" on this one 😁 I'm excited to add this feature to my workflow for a few months to really see if the 10 PR limit is even that much of an issue 💭 With all that in mind, I'll work on giving this another review ❤️ |
This reverts commit 36eaf14.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 Hey @nilvng - thank you for all the effort and collaboration in this PR ✨ ❤️ Apologies it took so long! I appreciate you taking the leap into implementing an old issue like this and tackling so many unknowns along the way.
I've done another thorough pass, and I think everything looks good and functions well! 😁 🚀
Thank you again for all the work, patience, ideas, and attention to detail ❤️
|
Hi @BagToad 👋 |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [cli/cli](https://github.com/cli/cli) | minor | `v2.66.1` -> `v2.67.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>cli/cli (cli/cli)</summary> ### [`v2.67.0`](https://github.com/cli/cli/releases/tag/v2.67.0): GitHub CLI 2.67.0 [Compare Source](cli/cli@v2.66.1...v2.67.0) #### `gh pr checkout` now supports interactively selecting a pull request Similar to commands like `gh workflow run` which prompts for a workflow to run, now `gh pr checkout` will prompt for a pull request to checkout. The list is currently limited to the most recent 10 pull requests in the repository. https://github.com/user-attachments/assets/0b2e3761-7318-4573-8a23-ae6f1a44b018 Big thank you to [@​nilvng](https://github.com/nilvng) for implementing this 🙌 #### Contributing guidelines updated We've updated our [`CONTRIBUTING.md`](https://github.com/cli/cli/blob/trunk/.github/CONTRIBUTING.md) guidelines to give more clarity around old `help wanted` issues. *TLDR*: - Please directly mention `@cli/code-reviewers` when an issue you want to work on does not have clear Acceptance Criteria - Please only open pull requests for issues with *both* the help wanted label and clear Acceptance Criteria - Please avoid expanding pull request scope to include changes that are not described in the connected issue's Acceptance Criteria Note: Acceptance Criteria is posted as an issue comment by a core maintainer. See cli/cli#10381 and cli/cli#10395 for more information. ❓ Have feedback on anything? We'd love to hear from you in a discussion post ❤️ #### What's Changed ##### ✨ Features - feat: let user select pr to checkout by [@​nilvng](https://github.com/nilvng) in cli/cli#9868 - feat: Add support for deleting autolink references by [@​hoffm](https://github.com/hoffm) in cli/cli#10362 - \[gh extensions install] Improve help text and error message by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10333 - Error when `gh repo rename` is used with a new repo name that contains an owner by [@​timrogers](https://github.com/timrogers) in cli/cli#10364 - Attestation bundle fetch improvements by [@​malancas](https://github.com/malancas) in cli/cli#10233 - \[gh project item-list] Add `iterationId` field in ProjectV2ItemFieldIterationValue by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10329 ##### 🐛 Fixes - \[gh api] Fix mutual exclusion messages of `--slurp` flag by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10332 - Exit with error if no matching predicate type exists by [@​kommendorkapten](https://github.com/kommendorkapten) in cli/cli#10421 - Do not try to parse bodies for HEAD requests by [@​jsoref](https://github.com/jsoref) in cli/cli#10388 - \[gh project item-edit] Fix number type by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10374 - \[gh workflow run] Improve error handling for `--ref` flag by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10328 - \[gh config] Escape pipe symbol in Long desc for website manual by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10371 ##### 📚 Docs & Chores - Fix logic error in contributing docs by [@​BagToad](https://github.com/BagToad) in cli/cli#10395 - Docs: Clarify guidelines for `help wanted` issues and pull requests by [@​BagToad](https://github.com/BagToad) in cli/cli#10381 - \[gh pr status] Mention `gh pr checks` in the `Long` section by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10389 - \[docs/releasing.md] Add basic info for homebrew update flow by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10344 - \[gh issue/pr list] Improve help text by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10335 - Remove v1 project 'add to board' automation from prauto workflow by [@​hoffm](https://github.com/hoffm) in cli/cli#10331 - Note: the following pair of MRs was reverted and never made into a release - \[gh repo edit] Allow setting commit message defaults by [@​iamazeem](https://github.com/iamazeem) in cli/cli#10363 - Revert "\[gh repo edit] Allow setting commit message defaults" by [@​BagToad](https://github.com/BagToad) in cli/cli#10372 #####Dependencies - Bump google.golang.org/protobuf from 1.36.4 to 1.36.5 by [@​dependabot](https://github.com/dependabot) in cli/cli#10379 **Full Changelog**: cli/cli@v2.66.1...v2.67.0 </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE2NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
fixes #2329
Acceptance criteria
If no argument specified for
pr checkoutand we're attached to a TTY,prompter.SelectFunctionality:
Users can then select the desired PR from the list using the arrow keys and confirm selection with Enter.
Iteration history
TTY
Screen.Recording.2024-12-09.at.9.25.09.pm.mov
Non TTY

I'd like to thank @BagToad for their constructive feedback throughout the development of this feature.
Second iteration
First iteration