Skip to content

fix(server): fetch change request heads from each host's ref namespace - #6532

Open
lnieuwenhuis wants to merge 3 commits into
pingdotgg:mainfrom
lnieuwenhuis:dev/fix/gitlab-merge-request-refs
Open

fix(server): fetch change request heads from each host's ref namespace#6532
lnieuwenhuis wants to merge 3 commits into
pingdotgg:mainfrom
lnieuwenhuis:dev/fix/gitlab-merge-request-refs

Conversation

@lnieuwenhuis

@lnieuwenhuis lnieuwenhuis commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What Changed

fetchPullRequestBranch and fetchPullRequestHeadCommit in GitVcsDriverCore no longer build
GitHub's refs/pull/<n>/head themselves. They now take the ref to fetch, and
GitManager.preparePullRequestThread names it from the host that resolved the change request:

  • github -> refs/pull/<n>/head (unchanged)
  • gitlab -> refs/merge-requests/<n>/head
  • bitbucket / azure-devops -> no such ref exists, so the head branch is fetched from the
    primary 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-control
helpers in apps/server/src/sourceControl/SourceControlProvider.ts. The host kind comes from
ChangeRequest.provider on the change request that preparePullRequestThread has already
resolved, 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 under refs/pull, so the fetch exits 128 with
fatal: couldn't find remote ref refs/pull/533/head. Both the primary and the fallback attempt in
materializePullRequestHeadBranch ran the same hardcoded refspec, so both failed identically and
preparePullRequestThread failed for every MR in worktree mode. Local mode was unaffected because
it goes through the provider's own checkoutChangeRequest. Azure DevOps and Bitbucket were broken
the same way, since neither publishes refs/pull/<n>/head either.

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:

  • A changeRequestHeadRef method on the SourceControlProvider service. Four provider
    implementations plus two registry wrappers (unsupportedProvider, bindProviderContext) would
    each need an entry, and every one would return a constant derived from the kind. The caller
    already has the kind; the indirection buys nothing.
  • Detecting the host from the remote URL inside the git driver. Self-hosted GitLab is not
    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.
  • Always fetching headBranch by name (the issue's third suggestion). It fixes same-repository
    MRs 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/head and nowhere
else, 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.ts
  • cd apps/server && pnpm exec tsgo --noEmit
  • pnpm exec vp fmt --check and pnpm exec vp lint on the changed files

Both new GitManager tests were confirmed to fail against the old ref layout (exit 128 on
GitVcsDriver.fetchPullRequestBranch, both attempts) before the fix, and the existing GitHub tests
that publish to refs/pull/<n>/head still pass.

Closes #6448

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes — no UI change
  • I included a video for animation/interaction changes — not applicable

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 changeRequestHeadRef so preparePullRequestThread picks the host-specific head ref from the resolved change request's provider (GitLab → refs/merge-requests/<iid>/head; Bitbucket/Azure DevOps → none). GitVcsDriver fetch APIs now take a generic headRef instead 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 GitVcsDriver

  • Adds changeRequestHeadRef to SourceControlProvider.ts mapping each provider kind to its head ref: refs/pull/<n>/head for GitHub, refs/merge-requests/<iid>/head for GitLab, null for Bitbucket, Azure DevOps, and unknown
  • Replaces prNumber with headRef: string in GitFetchPullRequestBranchInput and GitFetchPullRequestHeadCommitInput so fetchPullRequestBranch and fetchPullRequestHeadCommit in GitVcsDriverCore.ts fetch the provider-appropriate ref
  • materializePullRequestHeadBranchBase in 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 a GitManagerError for cross-repo PRs where the head repo cannot be resolved
  • Behavioral Change: cross-repo PRs on hosts without a head ref (Bitbucket) now surface a specific GitManagerError instead of a generic fetch failure; the dual-attempt fallback is skipped when headRef is null

Macroscope summarized 09dfa4e.

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.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 95795b10-7959-418f-b114-a09b2247bff5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 13, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 09dfa4e

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.

@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

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.

@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

Reopening — I re-checked against current main and the staleness worry that made me close this was wrong.

  • The bug is still there: GitVcsDriverCore.ts still hardcodes refs/pull/<n>/head at both fetch sites, and there is no refs/merge-requests/<iid>/head handling anywhere in apps/server/src. GitLab merge request heads still can't be fetched.
  • The no-head-ref fallback that landed in feat: multi-provider pull requests page with in-app reviews #4849 is already accounted for by this branch — it predates the branch point, so this isn't a duplicate of it.
  • The branch still merges cleanly into current main, and every fetchPullRequestBranch/fetchPullRequestHeadCommit call site in the merged tree uses the new headRef field — no caller was added on main that the signature change would miss.

@t3dotgg t3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

@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?

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/server/src/git/GitManager.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Worktree checkout of GitLab merge requests always fails — fetchPullRequestBranch hardcodes GitHub's refs/pull/<n>/head

2 participants