Skip to content

Delete a merged PR's branch only in its own repository - #2086

Merged
ppXD merged 1 commit into
mainfrom
fix/delete-a-merged-branch-only-in-its-own-repository
Oct 7, 2026
Merged

ppXD merged 1 commit into
mainfrom
fix/delete-a-merged-branch-only-in-its-own-repository

Conversation

@ppXD

@ppXD ppXD commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • git.merge_pr with deleteSourceBranch on GitHub used to delete heads/{head.ref} in the base repository even when the pull request's head lived in a fork. An outsider who named a fork branch after a real base branch (release, a teammate's feature) got that base branch deleted on merge. The provider now deletes only when the head repository id equals the base repository id (GitHubRepositoryProvider.IsHeadInBaseRepository). A fork's head, or a head whose fork has since been deleted, is kept and reported as SkippedFork.
  • The cleanup no longer swallows or throws. Its outcome is reported on RemotePullRequestMergeResult and in the node's outputs as sourceBranchDeletion (NotRequested / Deleted / Requested / SkippedFork / Failed) and sourceBranchDetail, whether the delete was refused (for example a protected branch), a read failed, or the cleanup was cancelled. A refused delete counts as deleted only when a branch read answers 404. A failed read leaves the refusal standing. A cancel during the cleanup returns the merge with Failed: the detail says "cancelled before GitHub confirmed it" and does not claim the branch was or was not deleted. A refusal that arrives while a cancel is pending is still reported, not raised as a failed merge.
  • GitLab does not have the defect: should_remove_source_branch is applied server-side to the merge request's own source project. The provider now reports it as Requested.

Test plan

  • Unit MergeSourceBranchTests: real Octokit/NGitLab provider against a loopback forge (StubProviderHost). Rows: same-repository delete (4 DELETE answers); fork kept, with the fork still present and with it deleted; refused delete surfaced; PR read-back failure surfaced; refused delete with the branch read failing (502, 403) reported Failed, not Deleted; cleanup cancelled during the DELETE (answer lost, and refused), with the merge returned and no second DELETE; nothing read or deleted when no delete was asked for; GitLab sends the flag and deletes nothing itself
  • Unit GitMergePullRequestNodeTests: output shape for every SourceBranchDeletion value; the output schema enum matches SourceBranchDeletion
  • Unit GitHubWriteRetryTests: a retried merge still deletes and reports Deleted
  • Mutations: widening the branch read-back's catch (NotFoundException) to ApiException, or dropping the cancellation clause, turns the new rows red
  • Integration PullRequestMergeSourceBranchFlowTests (Postgres): 2/2
  • Full unit suite: 12350 passed, 1 skipped; dotnet build: 0 errors

@ppXD
ppXD changed the base branch from fix/fail-gitlab-ci-checks-closed to main October 7, 2026 19:18
@ppXD
ppXD force-pushed the fix/delete-a-merged-branch-only-in-its-own-repository branch from 59fb645 to ad1a095 Compare October 7, 2026 19:18
GitHub never deletes a head branch on merge, so the provider deleted
heads/{head.ref} itself, in the base repository. For a fork's pull
request that ref names a different branch: an outsider who named their
fork branch after a real base branch (release, a teammate's feature) got
that base branch deleted when the pull request was merged with
deleteSourceBranch. A refused delete was swallowed, and a failure to
read the pull request back after the merge threw, which read as a
failed merge.

The provider now deletes only when the head repository id equals the
base repository id. A fork's head, or one whose repository GitHub no
longer reports, is kept. What happened is reported on the merge result
and in git.merge_pr's outputs (sourceBranchDeletion and
sourceBranchDetail) instead of being swallowed or thrown. A refused
delete of a branch that is already gone (a retried delete that landed,
or the repository's own auto-delete) reads as deleted, so a lost answer
is not reported as a failure. Only a 404 on the branch read-back counts
as gone: a read that fails proves nothing, so the refusal stands and a
protected branch is never reported deleted.

A cancel during the cleanup stops it and is reported as Failed, with
the merge result still returned. Letting it escape dropped a merge that
had landed, and a refused delete arriving while the cancel was pending
escaped as a provider error that git.merge_pr describes as a failed
merge. A delete in flight when the cancel lands is not confirmed either
way, so the detail says cancelled rather than not deleted.

GitLab does not share the defect: should_remove_source_branch is acted
on server-side against the merge request's own source project, so the
provider reports it as requested rather than deleted.
@ppXD
ppXD merged commit 0727989 into main Oct 7, 2026
2 checks passed
@ppXD
ppXD deleted the fix/delete-a-merged-branch-only-in-its-own-repository branch October 7, 2026 19:23
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.

1 participant