Closes a "non-fast-forward ref overwrite" path where a subsequent merger could orphan a previously-merged squash by advancing the integration branch to a sibling commit. Symptom (observed on fusion/fn-5419): main reflog shows385b6e93-> f6358ce4 (FN-5551 squash) ->63ec7098(FN-5552 squash) with f6358ce4 and63ec7098both parented at385b6e93. The FN-5551 squash was correctly committed to main, then the FN-5552 merger built its own squash off the stale385b6e93base and the CAS update-ref blindly moved main sideways, orphaning f6358ce4 onto whichever feature branch had already branched from it. Two coupled fixes uphold the missing invariant — local <integrationBranch> only advances via fast-forward, and the merger never builds a squash off a stale base sha: 1. advanceIntegrationBranchRef: add a `merge-base --is-ancestor` check before update-ref. Non-FF attempts now return reason: "non-fast-forward-advance" instead of overwriting the ref. The existing concurrent-advance CAS guard is retained. 2. runMerge: resolve the integration-branch tip via `git rev-parse --verify refs/heads/<integrationBranch>` instead of `git rev-parse HEAD` in rootDir. In reuse-task-worktree mode rootDir's HEAD can lag behind the shared ref after a sibling merger advanced it via update-ref without re-checking-out. Adds regression coverage in merger-ref-update-advance.test.ts: a sibling-commit advance with a matching expectedCurrentSha is now refused with the new reason, and multi-commit fast-forwards still succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1.3 KiB
@fusion/engine
| @fusion/engine |
|---|
| patch |
fix(merger): require fast-forward ref advances and read integration tip from refs/heads/<branch>
Closes a class of "orphaned merge" bug where a subsequent merger could overwrite the integration branch tip with a sibling commit, leaving the previous squash reachable only from a feature branch.
Two coupled fixes:
-
advanceIntegrationBranchRefnow refuses non-fast-forward advances. The CAS check still guards against concurrent ref movement, but the newmerge-base --is-ancestorcheck additionally requires the new sha to descend from the expected current sha. Non-FF attempts returnreason: "non-fast-forward-advance"instead of silently orphaning the prior tip. -
runMergeresolves the integration-branch tip viagit rev-parse --verify refs/heads/<integrationBranch>instead ofgit rev-parse HEADinrootDir. In reuse-task-worktree mode,rootDir's HEAD can lag behind the shared ref after a sibling merger advanced it viaupdate-refwithout re-checking-out — using HEAD there caused the eventual squash commit to parent off an earlier sha and orphan the previously-merged tip.
Together these uphold the invariant: local <integrationBranch> only advances via fast-forward, and the merger never builds a squash off a stale base sha.