fix(server): fetch change request heads from each host's ref namespace - #6532
fix(server): fetch change request heads from each host's ref namespace#6532lnieuwenhuis wants to merge 3 commits into
Conversation
Worktree checkout of a GitLab merge request failed for every MR, because fetchPullRequestBranch and fetchPullRequestHeadCommit built GitHub's refs/pull/<n>/head themselves. GitLab publishes the head at refs/merge-requests/<iid>/head, so the fetch exited 128 and both the primary and the fallback attempt failed identically. The driver now takes the ref to fetch and GitManager names it from the host that resolved the change request. Hosts that publish no such ref at all (Bitbucket, and Azure DevOps, which publishes only the merge result) fetch the head branch from the primary remote instead, except for a cross-repository head, where a branch of the same name there is somebody else's work.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused worktree bug fix that routes change-request head fetches through each host’s ref namespace, with bounded fallback behavior for hosts that publish no head ref. Production changes stay within the existing Git/source-control workflow and are accompanied by targeted integration coverage. You can add or adjust custom eligibility rules. Learn more. |
|
Closing this one — open since Aug 13 with no human review, and the git manager / VCS driver code it touches has moved on main since. Happy to reopen against current main if the per-host ref namespace fix is still wanted. |
|
Reopening — I re-checked against current
|
|
@t3dotgg @juliusmarminge I'd still like your thoughts on this, you can close this PR if it's not a worry for you, but every bug squashed is one less banana peel for the future, right? |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8b5abad. Configure here.

What Changed
fetchPullRequestBranchandfetchPullRequestHeadCommitinGitVcsDriverCoreno longer buildGitHub's
refs/pull/<n>/headthemselves. They now take the ref to fetch, andGitManager.preparePullRequestThreadnames it from the host that resolved the change request:github->refs/pull/<n>/head(unchanged)gitlab->refs/merge-requests/<n>/headbitbucket/azure-devops-> no such ref exists, so the head branch is fetched from theprimary remote by name instead. Skipped for a cross-repository head, where a branch of the same
name on the primary remote is somebody else's work; that case now reports why it cannot resolve
the head rather than failing on a fetch that could never have worked.
The mapping is one pure function,
changeRequestHeadRef, next to the other pure source-controlhelpers in
apps/server/src/sourceControl/SourceControlProvider.ts. The host kind comes fromChangeRequest.provideron the change request thatpreparePullRequestThreadhas alreadyresolved, so nothing extra is looked up and the ref can never disagree with the number it was
built from.
Why
Worktree checkout of a GitLab merge request fails for every MR (#6448). GitLab publishes the head
at
refs/merge-requests/<iid>/head, never underrefs/pull, so the fetch exits 128 withfatal: couldn't find remote ref refs/pull/533/head. Both the primary and the fallback attempt inmaterializePullRequestHeadBranchran the same hardcoded refspec, so both failed identically andpreparePullRequestThreadfailed for every MR in worktree mode. Local mode was unaffected becauseit goes through the provider's own
checkoutChangeRequest. Azure DevOps and Bitbucket were brokenthe same way, since neither publishes
refs/pull/<n>/headeither.The head ref is host knowledge, and the git driver has none: it only knows a repository path and a
remote name. The provider registry already resolves the host for the cwd, and the resolved change
request already carries its kind, so the smallest honest fix is to let the caller name the ref and
keep the driver purely git-level.
Alternatives considered and rejected:
changeRequestHeadRefmethod on theSourceControlProviderservice. Four providerimplementations plus two registry wrappers (
unsupportedProvider,bindProviderContext) wouldeach need an entry, and every one would return a constant derived from the kind. The caller
already has the kind; the indirection buys nothing.
reliably identifiable from its URL, which is exactly why the registry has a discovery probe to
refine an unknown remote. It would also duplicate provider detection in the git layer.
headBranchby name (the issue's third suggestion). It fixes same-repositoryMRs but gives up what the published ref is for: closed merge requests, heads whose branch was
deleted or renamed, and fork heads. It is used here only for hosts that publish no ref at all.
Verification
No real self-hosted GitLab remote was exercised — there is none available here. The new tests build
real local git repositories and publish the head under
refs/merge-requests/533/headand nowhereelse, which is the shape that made the original fetch fail.
cd apps/server && pnpm exec vp test run src/git/GitManager.test.ts src/sourceControl/SourceControlProvider.test.tscd apps/server && pnpm exec tsgo --noEmitpnpm exec vp fmt --checkandpnpm exec vp linton the changed filesBoth new
GitManagertests were confirmed to fail against the old ref layout (exit 128 onGitVcsDriver.fetchPullRequestBranch, both attempts) before the fix, and the existing GitHub teststhat publish to
refs/pull/<n>/headstill pass.Closes #6448
Checklist
Note
Medium Risk
Changes how PR/MR heads are fetched and materialized for worktree threads across all SCM hosts, though behavior is covered by new integration tests.
Overview
Fixes worktree-mode PR/MR threads failing on non-GitHub hosts because git fetches always used GitHub's
refs/pull/<n>/head.Adds
changeRequestHeadRefsopreparePullRequestThreadpicks the host-specific head ref from the resolved change request's provider (GitLab →refs/merge-requests/<iid>/head; Bitbucket/Azure DevOps → none).GitVcsDriverfetch APIs now take a genericheadRefinstead of a PR number.When no published head ref exists, materialization fetches the head branch on the primary remote; cross-repo heads without a resolvable fork repo fail early with an explicit message instead of a doomed GitHub-style ref fetch. Reused worktree refresh follows the same provider-aware ref vs upstream-branch logic, and hosts without a head ref skip the old GitHub ref fallback on materialization errors.
Reviewed by Cursor Bugbot for commit 09dfa4e. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fetch change request heads from host-specific ref namespace in
GitVcsDriverchangeRequestHeadRefto SourceControlProvider.ts mapping each provider kind to its head ref:refs/pull/<n>/headfor GitHub,refs/merge-requests/<iid>/headfor GitLab,nullfor Bitbucket, Azure DevOps, and unknownprNumberwithheadRef: stringinGitFetchPullRequestBranchInputandGitFetchPullRequestHeadCommitInputsofetchPullRequestBranchandfetchPullRequestHeadCommitin GitVcsDriverCore.ts fetch the provider-appropriate refmaterializePullRequestHeadBranchBasein GitManager.ts uses the host ref when available, falls back to remote-tracking branch for same-repo PRs on hosts with no head ref, and returns aGitManagerErrorfor cross-repo PRs where the head repo cannot be resolvedGitManagerErrorinstead of a generic fetch failure; the dual-attempt fallback is skipped whenheadRefis nullMacroscope summarized 09dfa4e.