fix(engine,dashboard): close 7 review findings on merger auto-sync
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>
This commit is contained in:
@@ -238,21 +238,29 @@ export type GitMutationType =
|
||||
* newSha?: string;
|
||||
* worktreePath?: string;
|
||||
* outcome:
|
||||
* | "clean-pull"
|
||||
* | "stash-pull-pop"
|
||||
* | "stash-pop-conflict"
|
||||
* | "skipped-dirty"
|
||||
* | "skipped-not-on-branch"
|
||||
* | "failed"
|
||||
* | "enumeration-failed"
|
||||
* | "exception";
|
||||
* stashSha?: string;
|
||||
* stashLabel?: string;
|
||||
* conflictedFiles?: string[];
|
||||
* stage?: "stash" | "pull" | "pop";
|
||||
* | "clean-sync" // worktree was clean against previousSha; reset --hard HEAD snapped it forward
|
||||
* | "synced-with-edits-restored" // real edits captured as patch, snapped to HEAD, patch re-applied cleanly
|
||||
* | "synced-with-pop-conflict" // patch failed to reapply OR untracked file collided with newly-tracked path
|
||||
* | "skipped-dirty" // ff-only mode + real edits → no-op (banner surfaces for manual handling)
|
||||
* | "skipped-not-on-branch" // worktree's HEAD is on a different branch than integrationBranch
|
||||
* | "skipped-head-not-at-new-sha" // concurrent advance moved HEAD past newSha between guard and reset
|
||||
* | "failed" // git command exited non-zero; see stage + error
|
||||
* | "enumeration-failed" // `git worktree list --porcelain` failed in the project root
|
||||
* | "exception"; // syncWorktreeToHead threw outside its own try/catch
|
||||
* stashedFiles?: string[]; // tracked-file edits captured into patchPath
|
||||
* patchPath?: string; // /tmp/fusion-worktree-sync-<id>/edits.patch (preserved when outcome surfaces a conflict)
|
||||
* conflictedFiles?: string[]; // paths git apply --3way couldn't reconcile; falls back to patch-header parsing when the index has no unmerged entries
|
||||
* untrackedRestored?: string[]; // untracked files copied back into the worktree after the snap
|
||||
* untrackedSkippedAsTracked?: string[]; // untracked files whose paths collided with newly-tracked files at HEAD; left in the stage dir
|
||||
* stage?: "snapshot" | "reset" | "apply" | "untracked-restore"; // only on outcome === "failed"
|
||||
* error?: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Per-step `pull:fast-forward`, `stash:push`, `stash:pop`, and
|
||||
* `stash:pop-conflict` events that flow through the merger's auditor as
|
||||
* part of this auto-sync carry `metadata.autoSync = true` so consumers can
|
||||
* filter them apart from user-triggered git operations.
|
||||
*/
|
||||
| "merge:auto-sync"
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user