Data-loss fixes in syncWorktreeToHead:
- Untracked-restore checks `git ls-tree -r --name-only HEAD` to skip
paths the new tip added as tracked files; user bytes stay in the
stage dir instead of clobbering merged content.
- Apply-failure on a deleted/renamed file: conflictedFiles falls back
to parsing `diff --git a/<p> b/<p>` headers when --diff-filter=U
returns nothing.
- All git invocations pass `-c core.quotePath=false` so non-ASCII
paths round-trip through copyFileSync.
- Stash-and-ff re-verifies rev-parse HEAD === newSha right before
each `reset --hard HEAD` (TOCTOU). On mismatch we bail with patch
preserved on disk.
- Stage dir lifecycle moved into try/finally with preserveStageDir
flag — kept whenever the user's edits live only in patchPath; rm'd
on all clean exits.
- Patch written to disk before the apply attempt, not only on
failure, so a crash between snapshot and apply doesn't lose edits.
Multi-worktree-same-branch fix:
- New getRegisteredWorktreeBranches returns Array<{branch,path}>
instead of collapsing into a Map. Multiple worktrees can share a
branch via `git worktree add --force -b`; merger now syncs all of
them rather than silently skipping all but the last.
Contract + surfacing fixes:
- JSDoc on merge:auto-sync GitMutationType now lists the actually-
emitted outcome strings + stage enum.
- GET /api/tasks/merge-advance-events joins merge:auto-sync events
within ±5min of the advance and returns them in a new
`autoSync: AutoSyncOutcome[]` field; useMergeAdvanceNotice exposes
the same shape so the banner can surface pop-conflicts (including
patchPath) instead of dropping them.
Hygiene:
- Merger now reads the setting via normalizeMergeAdvanceAutoSyncMode
instead of an inline check + `as unknown` cast.
New tests:
- Untracked-collides-with-tracked preserves merged content.
- Apply failure on deleted file populates conflictedFiles from
patch header.
- Route surfaces autoSync outcomes (clean-sync + pop-conflict)
joined within the time window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.5 KiB
3.5 KiB
@fusion/engine, @fusion/dashboard
| @fusion/engine | @fusion/dashboard |
|---|---|
| patch | patch |
fix(engine,dashboard): close 7 code-review findings on the mergeAdvanceAutoSync hook
Tightens the freshly-landed merger auto-sync feature based on a structured code review.
Data-loss fixes in syncWorktreeToHead:
- Untracked-file restore now compares against
git ls-tree -r --name-only HEADto detect when the new tip introduced a tracked file at the same path; collisions are reported inuntrackedSkippedAsTrackedand the user's bytes stay in the stage dir instead of clobbering the merged content. - When
git apply --3wayfails because a patched file was deleted/renamed at the new tip (--diff-filter=Ureturns nothing because nothing got staged),conflictedFilesfalls back to parsingdiff --git a/<p> b/<p>headers out of the captured patch — so the conflict surfaces with the right file names instead of[]. git ls-files/diffcalls now pass-c core.quotePath=falseso paths with non-ASCII or special characters round-trip throughcopyFileSyncinstead of failing on backslash-escaped octal tokens.- The stash-and-ff path re-verifies
rev-parse HEAD === newShaimmediately before each destructivereset --hard HEAD; a concurrent merger advance now bails withskipped-head-not-at-new-sha(with the captured patch preserved on disk) instead of applying the patch against the wrong tree. - The stage dir is now tracked with a
preserveStageDirflag in atry/finally: it is rm'd on all clean paths and onskipped-head-not-at-new-shaexits, but preserved whenever the user's edits live only inpatchPath(pop-conflict, untracked-collides-with-tracked, reset failure, outer exception). - Patch is written to disk before the apply attempt, not only on failure, so a crash between snapshot and apply doesn't lose the user's edits.
Multi-worktree-same-branch fix:
- New
getRegisteredWorktreeBrancheshelper inworktree-pool.tsreturns ALL(branch, worktreePath)entries rather than collapsing duplicates into aMap<branch, path>. Multiple worktrees can legitimately share a branch when the user created secondary checkouts viagit worktree add --force -b; the merger now syncs every one of them instead of silently skipping all but the last.
Contract + surfacing fixes:
- JSDoc on
merge:auto-syncGitMutationType now documents the actually-emitted outcome strings (clean-sync,synced-with-edits-restored,synced-with-pop-conflict,skipped-*,failed,enumeration-failed,exception) and the actualstageenum, replacing the obsoletesmartPull-shaped strings. GET /api/tasks/merge-advance-eventsnow joinsmerge:auto-syncevents within a ±5min window of each advance and returns them in a newautoSync: AutoSyncOutcome[]field;useMergeAdvanceNoticeexposes the same shape so the banner can surface pop-conflicts (includingpatchPathpointing at the user's saved edits) instead of leaving them in a black hole.
Hygiene:
- Merger's setting read now uses
normalizeMergeAdvanceAutoSyncMode(settings.mergeAdvanceAutoSync)(the exported normalizer) instead of an inline equality check +as unknowncast that bypassed type-checking.
New backstop tests in merger-auto-sync.slow.test.ts:
- Untracked file colliding with a newly-tracked path is NOT overwritten and the merged content survives.
git apply --3wayfailure on a file deleted at the new tip populatesconflictedFilesfrom the patch header.
Route test asserts autoSync outcomes are joined onto the matching advance event within the time window.