Skip to content

Fix remote branch deletion for owner case mismatch - #14429

Merged
williammartin merged 4 commits into
trunkfrom
williammartin-validate-issue-14404
Sep 11, 2026
Merged

williammartin merged 4 commits into
trunkfrom
williammartin-validate-issue-14404

Conversation

@williammartin

@williammartin williammartin commented Sep 11, 2026 •

Copy link
Copy Markdown
Member

Fixes #14404

Description

The repository owner parsed from a Git remote can preserve user-supplied casing while GitHub returns the canonical login casing. The merge command compared these values case-sensitively, incorrectly treating a same-repository pull request as cross-repository and silently skipping remote branch deletion.

This now requests and trusts GitHub's isCrossRepository value rather than deriving the relationship from owner login strings. It also adds regression coverage for a casing-only mismatch.

How did you test this change?

I created a private test repository with automatic head-branch deletion disabled, set prompt to disabled, changed only the owner casing in remote.origin.url, and opened two pull requests from branches in the same repository.

With the baseline binary, I ran gh pr merge 1 --merge --delete-branch. The local branch was deleted, but an API lookup showed that the remote branch still existed.

With the changed binary, I ran gh pr merge 2 --merge --delete-branch. The command reported deleting both the local and remote branches, and an API lookup confirmed that the remote branch no longer existed.

The focused live acceptance script also passed against github.com using the gh-acceptance-testing organization:

$ GH_ACCEPTANCE_SCRIPT=pr-merge-merge-strategy.txtar \
  GH_ACCEPTANCE_HOST=github.com \
  GH_ACCEPTANCE_ORG=gh-acceptance-testing \
  GH_ACCEPTANCE_TOKEN="$(gh auth token --hostname github.com)" \
  go test -tags=acceptance -count=1 \
    -run '^TestAcceptance$/^pr$' ./acceptance
ok      github.com/cli/cli/v2/acceptance        34.182s

Terminal demonstration showing the reproduction setup, the baseline preserving the remote branch, and the changed binary deleting it

Key points

The pull request model already exposes IsCrossRepository, which is the domain abstraction the merge command needs. Using it avoids adding a repository-owner type or duplicating GitHub login equivalence rules. The merge query no longer requests headRepositoryOwner, which was only used for the removed comparison.

Notes for reviewers

Start with the requested fields and crossRepoPR initialization in NewMergeContext, then read TestDeleteRemoteBranchTreatsOwnerCaseAsSameRepository.

The reproduction and investigation are documented in #14404.

Authorship and follow-up

Who wrote this:

  • A human wrote it.
  • An agent wrote it under close human direction.
  • An agent wrote it independently, and no human has guided the implementation beyond the initial prompt.

Who answers review comments:

  • @williammartin will read and reply directly.
  • An agent will draft replies and @username will read them before they are posted.
  • Nobody has explicitly committed to replying.

Use case-insensitive owner comparison so casing differences in a Git remote do not make same-repository pull requests appear cross-repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 11, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The focused fix matches established repository semantics and has appropriate regression coverage.

Review tier: Balanced
Findings: None

What changed in this PR

Fixes remote branch deletion when owner casing differs between Git remotes and GitHub’s canonical login.

Changes:

  • Uses case-insensitive owner comparison.
  • Adds regression coverage for casing-only mismatches.
File Description
pkg/​cmd/​pr/​merge/​merge.go Corrects cross-repository detection.
pkg/​cmd/​pr/​merge/​merge_test.go Verifies remote deletion occurs despite owner-case differences.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

williammartin and others added 2 commits September 11, 2026 14:32
Rely on the pull request relationship reported by GitHub instead of comparing owner login strings, and strengthen the regression test around the emitted branch deletion request.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise the normal same-repository --delete-branch path and verify that gh removes both local and remote refs while repository-side automatic deletion is disabled.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rely on the fresh isolated repository's default branch-deletion setting instead of modifying repository-global state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@williammartin
williammartin enabled auto-merge (squash) September 11, 2026 15:45
@williammartin
williammartin merged commit 8fcd6a6 into trunk Sep 11, 2026
11 checks passed
@williammartin
williammartin deleted the williammartin-validate-issue-14404 branch September 11, 2026 15:55
ripta added a commit to ripta/prowling that referenced this pull request Sep 15, 2026
Renders the header and the collapsed description from the normalized
model, so the information architecture can finally be judged against real
pull requests rather than argued about.

Check rows lead with what needs acting on. Ten of the twenty-one names on
cli/cli#14429 report stale, from bot workflows that run on
pull_request_target rather than on every push, so ordering by name puts
the row that matters below the fold.

The derivation sits in core rather than beside the components. It reads
the model and returns plain data, which is what the extension needs too.

A check is known only from a run that was seen. Never-run therefore means
a run that registered and never started, not a check the branch expects
and never got; nothing in the response lists the second kind.

Collapsed lines render one per row with wrapping off. A wrapped line
claims rows the count never budgeted for, and a box that clips them draws
the overflow over its neighbours instead of hiding it.
@ege-arhan

Copy link
Copy Markdown

Reading through this fix, switching the same-repo check from a login string comparison to IsCrossRepository looks right — GitHub logins are case-insensitive but the old Go comparison was not, so SomeCoolProject vs somecoolproject took the fork path and tried to delete a branch that was never there. One thing I checked while here: the finder field list now requests isCrossRepository, and the new test pins ExpectFields accordingly, which is consistent. Worth a quick grep for other MockFinder setups that still distinguish same/cross-repo only via HeadRepositoryOwner login — with the new predicate those would now read as same-repo (zero value false) unless IsCrossRepository is set, as you did in the onlyLocally case. From what I can see the remaining callers set it, so no behavior change beyond the intended one.

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.

gh pr merge NNN --delete-branch fails to delete branch when repo owner from remote.origin.url case mismatches github's canonical owner

4 participants