Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@BagToad
Copy link
Member

@BagToad BagToad commented Feb 27, 2025

Fixes #575

Description

This adds support Git's @{push} revision syntax for determining head ref.

Acceptance Criteria

Given git rev-parse --abbrev-ref @{push} resolves
when I run gh pr create
then I should create a pr with the headRef pointing at the @[push} return value

Given the branch.<branchName>.pushremote key is set on my current branch
when I run gh pr create
then I should create a pr with the headRef pointing at the remote value in the branch.<branchName>.pushremote key and the current branch name

Given the remote.pushDefault key is set on my current repo
when I run gh pr create
then I should create a pr with the headRef pointing at the remote value in the remote.pushDefault key and the current branch name

Given git rev-parse --abbrev-ref @{push} resolves
and/or given the branch.<branchName>.pushremote key is set on my current branch
and/or given the remote.pushDefault key is set on my current repo
when I run gh pr create
then I should create a pr with the headRef pointing at the given values in the following priority order: @[push} -> branch.<branchName>.pushremote -> remote.pushDefault

Acceptance Tests

--- PASS: TestPullRequests (0.01s)
    --- PASS: TestPullRequests/pr-create-basic (8.67s)
    --- PASS: TestPullRequests/pr-checkout-by-number (8.80s)
    --- PASS: TestPullRequests/pr-create-from-manual-merge-base (8.90s)
    --- PASS: TestPullRequests/pr-checkout (8.93s)
    --- PASS: TestPullRequests/pr-comment (9.26s)
    --- PASS: TestPullRequests/pr-create-with-metadata (9.66s)
    --- PASS: TestPullRequests/pr-view-same-org-fork (11.64s)
    --- PASS: TestPullRequests/pr-create-from-issue-develop-base (11.97s)
    --- PASS: TestPullRequests/pr-view-outside-repo (6.51s)
    --- PASS: TestPullRequests/pr-create-respects-remote-colon-branch-syntax (15.72s)
    --- PASS: TestPullRequests/pr-list (7.05s)
    --- PASS: TestPullRequests/pr-view-status-respects-remote-pushdefault (17.42s)
    --- PASS: TestPullRequests/pr-view-status-respects-push-destination (8.35s)
    --- PASS: TestPullRequests/pr-view (6.52s)
    --- PASS: TestPullRequests/pr-merge-rebase-strategy (9.83s)
    --- PASS: TestPullRequests/pr-merge-merge-strategy (9.97s)
    --- PASS: TestPullRequests/pr-checkout-with-url-from-fork (12.15s)
    --- PASS: TestPullRequests/pr-view-status-respects-simple-pushdefault (8.59s)
    --- PASS: TestPullRequests/pr-create-without-upstream-config (6.96s)
    --- PASS: TestPullRequests/pr-create-respects-simple-pushdefault (7.51s)
    --- PASS: TestPullRequests/pr-create-respects-push-destination (15.02s)
    --- PASS: TestPullRequests/pr-create-respects-branch-pushremote (14.77s)
    --- PASS: TestPullRequests/pr-view-status-respects-branch-pushremote (16.00s)
    --- PASS: TestPullRequests/pr-create-respects-remote-pushdefault (15.02s)
PASS
ok      github.com/cli/cli/v2/acceptance        34.077s

Notes

A lot of review for this PR happened on #10621

@BagToad BagToad requested review from Copilot and jtmcg February 27, 2025 17:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Overview

This PR adds support for Git's @{push} revision syntax for determining the head reference when creating pull requests. Key changes include updating the push destination logic in create.go, modifying test stubs in create_test.go to support the new git commands, and updating documentation in finder.go regarding PR reference order.

Reviewed Changes

File Description
pkg/cmd/pr/create/create.go Refactored push destination prompting and remote branch determination logic
pkg/cmd/pr/create/create_test.go Updated test stubs to simulate the new @{push} revision syntax command responses
pkg/cmd/pr/shared/finder.go Updated comment to reflect the correct PR ref direction for head and base repositories

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

pkg/cmd/pr/create/create_test.go:1678

  • The removal of Test_tryDetermineTrackingRef may reduce test coverage for tracking reference resolution. Consider adding tests that specifically validate the new behavior for push revision resolution.
func Test_tryDetermineTrackingRef(t *testing.T) {

Tip: Turn on automatic Copilot reviews for this repository to get quick feedback on every pull request. Learn more

Copy link
Contributor

@jtmcg jtmcg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start! Looks like you've managed to parse the complexity of how the context is using refs. I was sort of imaging this leaned a bit more into PRRefs, though, and had less duplication of its internal primitives (BranchName, HeadRepo, BaseRepo) floating around.

That's definitely a bigger refactor, but I think you've taken the right partial step there to follow through with it. Happy to pair on that if you'd like.

And, of course, if it isn't possible, let me know! I'd love to try to update PRRefs to make it possible, though, and I'm happy to help with that as well!

@BagToad BagToad marked this pull request as ready for review March 5, 2025 23:24
@BagToad BagToad requested a review from a team as a code owner March 5, 2025 23:24
@BagToad BagToad requested a review from andyfeller March 5, 2025 23:24
@BagToad BagToad requested review from Copilot and jtmcg March 5, 2025 23:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Overview

This PR implements support for Git’s @{push} revision syntax in the pull request creation flow. It updates the internal representation of PR references by replacing separate base and head branch fields with a unified PullRequestRefs struct, updates the forking and remote handling logic accordingly, and adjusts tests to accommodate changes in the push destination resolution process.

Reviewed Changes

File Description
pkg/cmd/pr/create/create.go Refactored how head ref is determined using the PullRequestRefs struct and updated push logic.
pkg/cmd/pr/create/create_test.go Updated command stubs and test expectations to reflect new push destination and branch parsing.
pkg/cmd/pr/shared/finder.go Updated comments to reflect the new representation of PR references.

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (2)

pkg/cmd/pr/create/create.go:914

  • The change in the forking condition now checks only ctx.forkHeadRepo and not whether a head repository was already determined. Please verify that this behavior is intended and that it correctly reflects the desired logic for automating forks.
if ctx.forkHeadRepo && ctx.isPushEnabled {

pkg/cmd/pr/shared/finder.go:102

  • [nitpick] The updated comment changes the direction of the PR representation. Please confirm that the new comment correctly describes the intended relationship between the head and base branches.
// headRef -----PR-----> baseRef

Tip: Copilot code review supports C#, Go, Java, JavaScript, Markdown, Python, Ruby and TypeScript, with more languages coming soon. Learn more

@andyfeller
Copy link
Member

On it! 🫡

Copy link
Member

@andyfeller andyfeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for delayed follow up on this 🙇

This is a pretty extensive set of changes to tricky logic and think it would benefit from 1) me making more time to really reason out the changes and 2) getting a 2nd set of eyes (@williammartin) that hasn't paired on developing the changes. Or maybe a synchronous code review 🤔

All of that said, I did have a handful of questions and commentary but will try to make time to dig deeper in the core of the refactor.

Copy link
Contributor

@jtmcg jtmcg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loving how this is shaping up! Good work propagating PRRefs through to the command 🙌 I've added some thoughts for your consideration and some questions.

Copy link
Contributor

@jtmcg jtmcg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hell yeah, nice work on this! I've run all the acceptance tests myself as well and have verified they are working as expected :shipit:

Copy link
Member

@andyfeller andyfeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for having more questions in digging into the stickier parts of the PR here. 😞 I fear the complexity of the code involved and repercussions in replacing out the logic involved and whether it has greater impacts beyond the original AC in #575 (comment).

Copy link
Member

@andyfeller andyfeller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final round of suggestions and thoughts pairing with @BagToad

We've resolved a number of conversations for now and so a few other little suggestions and will give this a ship! 💪

Comment on lines 1566 to 1590
setup: func(opts *CreateOptions, t *testing.T) func() {
opts.TitleProvided = true
opts.BodyProvided = true
opts.Title = "my title"
opts.Body = "my body"
opts.HeadBranch = "otherowner:feature"
return func() {}
},
customPushDestination: true,
cmdStubs: func(cs *run.CommandStubber) {
cs.Register("git rev-parse --abbrev-ref feature@{push}", 0, "origin/feature")
cs.Register("git config remote.pushDefault", 0, "")
cs.Register("git config push.default", 0, "")
},
expectedOut: "https://github.com/OWNER/REPO/pull/12\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: is there something else we can do in this test to communicate the upstream/origin nature of the test to help the next person understand?

In the other tests, opts.Remotes is missing here and acts as context in the others. Unsure what to do here but took me a hot minute to connect some of the dots. 🤷

Comment on lines 737 to 907
if prRefs.HeadRepo == nil && isPushEnabled && !opts.IO.CanPrompt() {
fmt.Fprintf(opts.IO.ErrOut, "aborted: you must first push the current branch to a remote, or use the --head flag")
return nil, cmdutil.SilentError
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about this conditional being impossible because prRefs.HeadRepo will likely never be nil 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it won't be because of this line in ParsePRRefs. That means we're assuming that the headRepo is the same as the baseRepo when it isn't specified in the config. I think that means we'll always push to the baseRepo unless you tell us otherwise. That is different behavior, but is it undesirable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm yeah 🤔 I don't know if it's undesirable or not.

If isPushEnabled is true here, it means that we prompted the user for a head repo and they selected an option. That means in this case, again, the HeadRepo is also never nil.

Previously prRefs.HeadRepo == nil && isPushEnabled would indicate to downstream in handlePush to fork... I don't see why we'd care about prompting because it looks like no prompting is happening later around where we fork.

With these proposed changes, we check forkHeadRepo instead:

if ctx.forkHeadRepo && ctx.isPushEnabled {
opts.IO.StartProgressIndicator()
headRepo, err = api.ForkRepo(client, ctx.PrRefs.BaseRepo, "", "", false)
opts.IO.StopProgressIndicator()
if err != nil {
return fmt.Errorf("error forking repo: %w", err)
}
didForkRepo = true
}

All this rambling is to say, I don't really understand what we were previously guarding against in the old code. I guess there was a case where we didn't have a HeadRepo but still had isPushEnabled set to true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this is uncovered in tests (maybe indicating it is impossible).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least at some point it was possible: #2979, #6468

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In past this would happen if:

  • The user did not provide -H
  • No tracking branch could be determined
  • Prompting wasn't possible

Like you point out, this guard prevents forking. Perhaps the idea is that forks shouldn't be created without user interaction?

Copy link
Member

@williammartin williammartin Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a new field to track forking is far clearer than relying on tracking the state through, thanks. I sort of think forkBaseRepo might be clearer though? That's the one we are forking, in order to create a headRepo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, if previously the idea here was that we would prevent forking the repo under the following conditions:

  • The user did not provide -H
  • No tracking branch could be determined
  • Prompting wasn't possible

And now prRefs.HeadRepo is always set, as Tyler mentions originally, that means in this case we will be pushing to the base repo. Maybe that's ok? I really don't know.

Copy link
Member

@williammartin williammartin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed everything except create.go and create_test.go, just getting some questions out ahead of time.

Comment on lines 737 to 907
if prRefs.HeadRepo == nil && isPushEnabled && !opts.IO.CanPrompt() {
fmt.Fprintf(opts.IO.ErrOut, "aborted: you must first push the current branch to a remote, or use the --head flag")
return nil, cmdutil.SilentError
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this is uncovered in tests (maybe indicating it is impossible).

BagToad and others added 19 commits April 15, 2025 13:38
Standardize <user>:<branch> syntax wherever it is described in comments.
Document the user:branch syntax for the `--head`` flag in `gh pr create`.
This test was setting `remote.pushDefault` which likely caused
the `user:branch` syntax to be ignored. Update the test to not set
this config value.
@BagToad BagToad force-pushed the kw/575-detect-push-target-for-local-branches-without-upstream-configuration branch from 8b67d4e to a9dbda6 Compare April 15, 2025 19:38
@BagToad BagToad merged commit d52a599 into trunk Apr 15, 2025
16 checks passed
@BagToad BagToad deleted the kw/575-detect-push-target-for-local-branches-without-upstream-configuration branch April 15, 2025 20:37
@gibfahn
Copy link

gibfahn commented Apr 18, 2025

I tested this out locally, and it seems to work great! Before I would always push and then have the gh CLI ask me if I wanted to push again:

git ppr
Enumerating objects: 35, done.
Counting objects: 100% (35/35), done.
Delta compression using up to 12 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (18/18), 3.41 KiB | 3.41 MiB/s, done.
Total 18 (delta 17), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (17/17), completed with 17 local objects.
remote:
remote: Create a pull request for 'update' on GitHub by visiting:
remote:      https://github.com/gibfahn/myrepo/pull/new/update
remote:
To https://github.com/gibfahn/myrepo.git
 * [new branch]      update -> updategh pr create ...
? Where should we push the 'update' branch?  [Use arrows to move, type to filter]
  upstream-org/myrepo
  gibfahn/myrepo
  Skip pushing the branch
> Cancel

Now it just works directly 🎉 . Looking forward to this shipping in a release.

@BagToad
Copy link
Member Author

BagToad commented Apr 18, 2025

We love to hear that @gibfahn! Thank you for trying it and writing your feedback ❤️ ✨

@williammartin
Copy link
Member

@gibfahn just curious (you may have described this elsewhere), what is your workflow / what are your git settings? Just nice to know which one of the settings we now respect solved this for you.

@gibfahn
Copy link

gibfahn commented Apr 18, 2025

I always have @{push} and @{upstream} set to the PR branch and the base branch respectively. This means especially in a configuration where I have e.g.

fork	https://github.mycorp.com/gibfahn/myrepo
pub	https://github.com/myorg/myrepo
pubfork	https://github.com/gibfahn/myrepo
up	https://github.mycorp.com/myorg/myrepo

I'll have some PRs that go to GitHub Enterprise, and some that go to github.com. So having the detection understand that makes things much simpler.

(in case it's useful I wrote up my full config at https://fahn.co/posts/a-better-pull-request-workflow-with-git-push-branches.html)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect push target for local branches without upstream configuration

6 participants