From 1dbb838af18a7aa09f642d6afb2d492891b5aac8 Mon Sep 17 00:00:00 2001 From: Alexander Adam Date: Fri, 25 Sep 2026 10:32:18 +0200 Subject: [PATCH] fix(gitea): collapse pull-ref-only heads to pr-N in local branch names buildForkLocalBranchName still used the raw headRef even when it was just the pull ref (refs/pull/N/head), which agit prs and deleted head branches report as their headRefName. that gave local branches literal names like refs/heads/refs/pull/3/head. --- .../src/utils/change-request-checkout.test.ts | 25 +++++++++++++++++++ .../src/utils/change-request-checkout.ts | 14 +++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/change-request-checkout.test.ts b/packages/server/src/utils/change-request-checkout.test.ts index b630ee15af..dbd9f55a0d 100644 --- a/packages/server/src/utils/change-request-checkout.test.ts +++ b/packages/server/src/utils/change-request-checkout.test.ts @@ -61,4 +61,29 @@ describe("buildForkLocalBranchName", () => { it("falls back to the pr number when the owner is not a safe ref segment", () => { expect(buildForkLocalBranchName({ ...fork, headOwnerLogin: "-zaphod" })).toBe("pr-42/patch-1"); }); + + // pull-ref-only heads (agit PRs, deleted branches) report headRef as the + // literal pull ref, e.g. refs/pull/42/head, not a real branch name + it("collapses a same-repository pull-ref-only head to pr-", () => { + expect( + buildForkLocalBranchName({ + ...fork, + isCrossRepository: false, + headOwnerLogin: "arthur", + hasHeadBranch: false, + }), + ).toBe("pr-42"); + }); + + it("prefixes pr- with the owner for a fork pull-ref-only head", () => { + expect( + buildForkLocalBranchName({ ...fork, headOwnerLogin: "Arthur", hasHeadBranch: false }), + ).toBe("arthur/pr-42"); + }); + + it("does not double the pr number when the fork owner is unknown and there is no head branch", () => { + expect(buildForkLocalBranchName({ ...fork, headOwnerLogin: null, hasHeadBranch: false })).toBe( + "pr-42", + ); + }); }); diff --git a/packages/server/src/utils/change-request-checkout.ts b/packages/server/src/utils/change-request-checkout.ts index f3f0b3feeb..7fc6e2190b 100644 --- a/packages/server/src/utils/change-request-checkout.ts +++ b/packages/server/src/utils/change-request-checkout.ts @@ -26,12 +26,22 @@ export interface ForkLocalBranchNameInput { number: number; isCrossRepository: boolean; headOwnerLogin: string | null; + /** false when headRef is a synthetic pull ref, not a real branch name (agit PRs, deleted branches) */ + hasHeadBranch?: boolean; } export function buildForkLocalBranchName(params: ForkLocalBranchNameInput): string { + const pullRefFallback = `pr-${params.number}`; + // headRef is the literal pull ref (e.g. refs/pull/42/head) when there is no + // real branch to name after, so use the pr number instead + const headSegment = params.hasHeadBranch === false ? pullRefFallback : params.headRef; if (!params.isCrossRepository) { - return params.headRef; + return headSegment; } const owner = normalizeForgeOwnerForBranch(params.headOwnerLogin); - return `${owner ?? `pr-${params.number}`}/${params.headRef}`; + if (!owner) { + // don't double up pr-/pr- when the owner is also unknown + return headSegment === pullRefFallback ? headSegment : `${pullRefFallback}/${headSegment}`; + } + return `${owner}/${headSegment}`; }