Files
fusion/.changeset/merger-auto-sync-hook.md
gsxdsm 4c31e885bd feat(engine): merger auto-syncs project-root checkout after ref advance
After advanceIntegrationBranchRef ff-updates refs/heads/<integrationBranch>,
the merger now enumerates other worktrees on that branch and reconciles
each one's index + working tree to the new tip via syncWorktreeToHead.

Not a git pull — origin may still be at the previous tip without
pushAfterMerge, so pull --ff-only is a no-op and a naive stash/pull/pop
ends with the worktree restored to the old state. Instead the new
worktree-ref-sync helper:

  1. Diffs the worktree against the previous tip to isolate real edits
     from the stale-index "phantom diff" against the new HEAD.
  2. Snaps clean worktrees forward via reset --hard HEAD.
  3. In stash-and-ff mode with real edits, captures them as a binary patch
     against the previous tip, snaps to HEAD, then git apply --3way to
     restore. Untracked files are saved + restored separately. Patch
     conflicts surface as synced-with-pop-conflict with the patch left on
     disk for manual recovery.

Per-worktree outcome emitted as merge:auto-sync (new GitMutationType).
Per-step pull:fast-forward / stash:push / stash:pop / stash:pop-conflict
that pass through the auditor are tagged metadata.autoSync=true.

Isolated in its own try-catch so an auto-sync failure can't fail the
already-landed merge. Default behavior is mergeAdvanceAutoSync="stash-and-ff";
"off" preserves the legacy surprise behavior.

Backstopped by merger-auto-sync.slow.test.ts: clean-sync snaps both index
and files forward, ff-only with real edits is a no-op, stash-and-ff
preserves untracked locals across the snap, task worktrees on fusion/fn-*
are skipped, empty branch map emits nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:41:11 -07:00

2.4 KiB

@fusion/engine, @fusion/core
@fusion/engine @fusion/core
minor patch

feat(engine): merger auto-syncs project-root checkout after advancing integration-branch ref

Wires mergeAdvanceAutoSync into the merger's post-ref-advance code path. After advanceIntegrationBranchRef ff-updates refs/heads/<integrationBranch>, the merger now enumerates other worktrees still on that branch (typically the user's project-root checkout) and reconciles each one's index + working tree to the new tip via syncWorktreeToHead.

The reconciliation primitive is not a git pull — origin may still be at the previous tip (no pushAfterMerge), in which case git pull --ff-only is a no-op and a naive stash → pull → pop ends with the worktree restored to the old state. Instead syncWorktreeToHead:

  1. Diffs the worktree against the previous tip to isolate real user edits from the stale-index "phantom diff" that looks like inverted commits.
  2. When the worktree is clean against the previous tip, runs git reset --hard HEAD to snap index + files forward.
  3. In stash-and-ff mode with real edits, captures them as a binary patch against the previous tip, snaps to HEAD, then git apply --3way to restore. Untracked files are copied to a temp dir and restored after the snap. Patch conflicts surface as synced-with-pop-conflict with the patch left on disk for manual recovery.

Each per-worktree attempt emits a merge:auto-sync audit event (new GitMutationType) with the outcome; the per-step pull:fast-forward, stash:push, stash:pop, and stash:pop-conflict events that pass through the auditor are tagged metadata.autoSync = true so downstream consumers can attribute them.

The user-facing effect: with the default mergeAdvanceAutoSync: "stash-and-ff", after a Fusion task merges the user's git status in the project-root checkout becomes clean and the working tree shows the new commits' content — no manual git reset or Pull-button click required. Set mergeAdvanceAutoSync: "off" to restore the legacy behavior (the Merge Advance Notice banner still surfaces and the user pulls by hand).

Backstopped by merger-auto-sync.slow.test.ts covering: clean-sync snaps both index and files forward, ff-only with real edits is a no-op, stash-and-ff preserves untracked local files across the snap, task worktrees on fusion/fn-* branches are correctly skipped, and an empty branch map emits nothing.