Stacked on !242. Last of the v1.13.0 code.
The bundle carried no head, so every replayed merge request had an empty
one. mr diff resolves through refs/merge-requests/<n>/head, never set,
and a merged MR has no source branch to fall back on — so it could only
fail. The bundle now carries head_sha, merged_base and merged_at, a
merged MR is replayed with MarkMerged (not SetMRState, which leaves
the base empty), and the ref is pointed at the head once objects exist.
Re-running an import repairs the ref: that path used to continue on an
already-imported MR before reaching any of this, which meant the
bundle-then-push order — the normal one — could never produce a working
diff.
GitHub half: the head was already recorded, and the ref set when objects
happened to be present, which for a mirror made with default refspecs
they are not. The import fetches refs/pull/*/head into refs/gh-pull/*
first, one fetch rather than one per PR, token via GIT_ASKPASS like the
mirror worker rather than in a URL in argv. It reports how many MRs ended
with no head objects.
Verification, honestly. You said to attempt both halves knowing the
GitHub path can't be exercised from here. The migrate half is covered by
TestMigratedMRHasADiff against two real instances — and worth noting,
my first version of that test passed without the fix, because with
the source branch still present the diff resolves through it and an empty
head is invisible. It uses a merged MR with a deleted branch now and
fails without the fix; I checked. The GitHub path is written against the
documented refs/pull/* behaviour and is unverified until you run a
real import.
Closes #128
retargeted from review-loop-e2e to main: !242 merged
2026-09-04 17:13 UTC