fix: use merge ref for PR discovery when push.default=simple by kaovilai · Pull Request #13011 · cli/cli · GitHub
Skip to content

fix: use merge ref for PR discovery when push.default=simple#13011

Closed
kaovilai wants to merge 1 commit intocli:trunkfrom
kaovilai:fix/pr-branch-detection-fork-same-name
Closed

fix: use merge ref for PR discovery when push.default=simple#13011
kaovilai wants to merge 1 commit intocli:trunkfrom
kaovilai:fix/pr-branch-detection-fork-same-name

Conversation

@kaovilai
Copy link
Copy Markdown

Summary

When push.default=simple (the default) and the local branch name differs from the remote tracking branch name (e.g., feature-branch-fork tracking origin/feature-branch), git rev-parse --abbrev-ref @{push} fails with "cannot resolve 'simple' push to a single destination".

The fallback code in tryDetermineDefaultPushTarget() was using the local branch name as the remote branch name, causing gh pr view and gh pr status to search for a non-existent branch like owner:feature-branch-fork instead of the correct owner:feature-branch.

This fix adds PushDefaultSimple to the condition that checks the merge ref from branch config (branch.<name>.merge), so the correct remote branch name is used for PR discovery. This is safe because at this point in the code, @{push} has already failed — meaning simple mode could not resolve, so the merge ref is the best source of truth for the remote branch name.

Common scenarios where this occurs:

  • Fork PRs where the head branch has the same name as the base branch (e.g., mainmain), causing gh pr checkout to create a prefixed local branch
  • Users who manually create local branches with different names tracking remote branches (e.g., git checkout -b my-local-name origin/remote-name)

Regression from

This is a regression from PR #9208 (commit 7fc35fd, merged Jan 2025, shipped in gh v2.85.0).

Fixes #12203
Fixes #7590

…sh} fails

When `push.default=simple` (the default) and the local branch name differs
from the remote tracking branch name, `git rev-parse --abbrev-ref @{push}`
fails with "cannot resolve 'simple' push to a single destination". The
fallback code was using the local branch name as the remote branch name,
causing `gh pr view` and `gh pr status` to search for a non-existent branch.

This fix adds `PushDefaultSimple` to the condition that checks the merge ref
from branch config, so the correct remote branch name is used for PR
discovery. This is safe because at this point in the code, @{push} has
already failed — meaning simple mode could not resolve, so the merge ref
is the best source of truth for the remote branch name.

This is a regression from PR cli#9208 (commit 7fc35fd).

Fixes cli#12203
Fixes cli#7590

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@kaovilai kaovilai requested a review from a team as a code owner March 24, 2026 03:16
@github-actions github-actions Bot added external pull request originating outside of the CLI core team needs-triage needs to be reviewed unmet-requirements and removed needs-triage needs to be reviewed labels Mar 24, 2026
@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown
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.

Pull request overview

This PR fixes PR discovery for gh pr view / gh pr status when push.default=simple and the local branch name differs from the remote tracking branch name by falling back to branch.<name>.merge (merge ref) to determine the correct remote head branch.

Changes:

  • Extend tryDetermineDefaultPushTarget() to use the merge ref for push.default=simple (in the @{push}-failed fallback path).
  • Update/refine unit tests to cover the push.default=simple merge-ref behavior, including a fork PR-style “local name != remote name” scenario.

Reviewed changes

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

File Description
pkg/cmd/pr/shared/find_refs_resolution.go Uses merge ref to derive the remote branch name when push.default=simple and @{push} can’t be resolved.
pkg/cmd/pr/shared/find_refs_resolution_test.go Adds coverage for push.default=simple and a fork/renamed-local-branch scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +338 to +340
// means the local branch name differs from the remote tracking branch name. In
// simple mode, git refuses to push when names differ, but for PR discovery we
// still want to use the remote branch name from the merge ref.
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

The rationale comment for the push.default=simple fallback says failing to resolve @{push} “means the local branch name differs from the remote tracking branch name”. PushRevision can fail for other reasons too (e.g., no upstream/push info configured), so this is stronger than what we can infer here. Suggest rewording to “can mean” / “in the mismatch case” and also updating the earlier function doc comment (above) that currently only mentions upstream/tracking as using branch.<name>.merge.

Suggested change
// means the local branch name differs from the remote tracking branch name. In
// simple mode, git refuses to push when names differ, but for PR discovery we
// still want to use the remote branch name from the merge ref.
// can mean the local branch name differs from the remote tracking branch name. In
// that mismatch case, git refuses to push when names differ in simple mode, but for
// PR discovery we still want to use the remote branch name from the merge ref.

Copilot uses AI. Check for mistakes.
@williammartin
Copy link
Copy Markdown
Member

williammartin commented Mar 24, 2026

@kaovilai,

It's been a while since I built my mental model of all the different push default behaviour so forgive me.

The push.default configuration. The default is push.default=simple, which will push to a branch with the same name as the current branch.

If push.default is simple, then by default, it seems reasonable for us to assume that the head of your PR is on a remote branch of the same name as your local branch. Using the merge ref as you're proposing here may just cause a bug in another direction, for example, if you set up a triangular workflow so that you are merging from main and pushing to feature, this will result in us trying to find the PR on the main branch.

We're trying to honor git's defaults here, and it seems like if you want something else, you should find another way to indicate that.

Lemme know your thoughts, like I said, it's been a while. Cheers.

@kaovilai
Copy link
Copy Markdown
Author

Note

Responses generated with Claude

Hey @williammartin, thanks for looking at this! Good question.

The key thing is that tryDetermineDefaultPushTarget() is only reached after @{push} has already failed to resolve at find_refs_resolution.go:319. So the question is: under push.default=simple, when does @{push} fail?

simple mode behavior:

  • Same remote for push and fetch (non-triangular): requires local branch name == upstream branch name. If they differ, @{push} fails with "cannot resolve 'simple' push to a single destination" — this is the scenario being fixed.
  • Different push remote (triangular): behaves like current mode, pushing to the same-named branch on the push remote. @{push} succeeds, so tryDetermineDefaultPushTarget() returns early at line 320 and never reaches the pushDefault check at line 342.

So your triangular workflow concern (merging from main, pushing to feature) wouldn't reach the merge ref fallback — PushRevision() would resolve successfully to pushRemote/feature and return the correct target on line 320.

The only case where execution reaches the PushDefaultSimple condition at line 342 is when the local branch name differs from the remote tracking branch on the same remote (e.g., local feature-branch-fork tracking origin/feature-branch). In that case, branch.<name>.merge (refs/heads/feature-branch) is exactly the correct remote branch name for PR discovery.

The most common scenario for this is gh pr checkout on a fork PR where the head branch has the same name as the base branch — gh creates a prefixed local branch name to avoid conflicts, but the remote branch name stays the same.

@williammartin
Copy link
Copy Markdown
Member

williammartin commented Mar 25, 2026

To be clear, when I describe a triangular workflow, I'm describing triangular within a single remote where we merge from one branch and push to another. For example:

[branch "feature"]
	remote = origin
	merge = refs/heads/main

In this case, with push.default=simple, @{push} will not resolve:

➜ git rev-parse --symbolic-full-name feature@{push}
fatal: cannot resolve 'simple' push to a single destination

But pushes may be done explicitly e.g. git push origin feature. In fact, there is an acceptance test for this behaviour: https://github.com/cli/cli/blob/trunk/acceptance/testdata/pr/pr-view-status-respects-simple-pushdefault.txtar

You say:

In that case, branch..merge (refs/heads/feature-branch) is exactly the correct remote branch name for PR discovery.

But this is not true for the use case outlined above. With this PR, we would instead use origin/main as the head rather than origin/feature. I'm going to close this PR because I do not believe it is the correct approach. I've left a comment over on #12203 (comment), please review that and we can continue the discussion from there and decide whether the issue should be labelled help-wanted. Thanks.

@kaovilai
Copy link
Copy Markdown
Author

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

Labels

external pull request originating outside of the CLI core team unmet-requirements

Projects

None yet

3 participants