fix(merger): require fast-forward ref advances and read integration tip from refs/heads/<branch>
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>
This commit is contained in:
15
.changeset/merger-ff-only-ref-advance.md
Normal file
15
.changeset/merger-ff-only-ref-advance.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
"@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:
|
||||
|
||||
1. `advanceIntegrationBranchRef` now refuses non-fast-forward advances. The CAS check still guards against concurrent ref movement, but the new `merge-base --is-ancestor` check additionally requires the new sha to descend from the expected current sha. Non-FF attempts return `reason: "non-fast-forward-advance"` instead of silently orphaning the prior tip.
|
||||
|
||||
2. `runMerge` resolves 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 — 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.
|
||||
Reference in New Issue
Block a user