fix(devutils): keep mtimes of files sync pops and pushes back unchanged
Problem. When a patch in the middle of the series changes or is removed, worktree.sh sync pops it and every patch after it, then pushes the new stack. Most popped files come back byte-identical but with new mtimes, so siso recompiles them. On the Chromium 156 base this blocks a needed change: Helium's lint rejects the empty rac/spaces/composited-transition.patch (which fails lint on every PR stacked on the sync branch), but dropping it mid-series would make every 156 clone recompile what the ~150 later patches touch.
Change.
- Before popping,
cmd_syncrunsworktree_state.py snapshot <keep>. It records the path, mtime and sha256 of every file the popped patches touch, plus a build marker: the mtimes ofout/*/.siso_fs_state,.ninja_log,build.ninjaandbuild.ninja.stamp. - After pushing, and before
dev_names sub,restoreputs the old mtime back on every file whose content is byte-identical. The restore runs in the same name state as the snapshot. Files whose content changed keep their new mtime. - If the tree was built or regenerated in between, nothing is restored.
- A retry after a failed push keeps the earlier record, but only if nothing was built since.
- A malformed record is dropped with a warning. Symlinks are skipped.
Verified.
test_worktree.py: 46 tests, including 7 new ones, all confirmed to fail onmain:- a changed middle patch;
- dropping an empty middle patch;
- a malformed record;
- a build in between;
- a
gn genin between; - a retry after a failed push;
- a retry after a build.
test_dev_names.py: 19 tests.- On a real Chromium 156 clone: I removed the empty patch from the series and ran
worktree.sh sync. It popped 149 patches and printedkept mtimes on 459 unchanged files (2 changed). The next build was 15 steps: 13 string steps (#35's job) and 2 compiles of the two files that really changed. Without the fix the same tree needs at least 1,107. Putting the line back gave461 unchanged (0 changed)and 15 steps again. - Devin SWE-2 wrote it. Claude Opus reviewed it, fixed three defects, and ran the real-tree proof. The defects: the build marker missed
gn gen, which is a real wrong-build risk; a retry lost most of the savings; and symlink handling.
Patch surface (D17): no change, because this PR touches no patches.
🤖 Generated with Claude Code
[!NOTE]
Medium Risk
Changes sync/build interaction and mtime restoration logic; incorrect restoration could hide real edits from siso, though build-marker and SHA256 checks mitigate that.
Overview
worktree.sh sync now snapshots file mtimes and content hashes before popping patches, then restores prior mtimes after the stack is pushed again for any file that ended up byte-identical—so siso does not treat harmless pop/push rewrites as edits.
worktree_state.py adds snapshot / restore and .rac_mtimes.json, recording paths touched by popped patches plus a build marker (e.g. out/*/.siso_fs_state, .ninja_log, build.ninja, build.ninja.stamp). If a build or gn gen ran between snapshot and restore, restoration is skipped to avoid stale outputs. Failed-sync retries can reuse an earlier snapshot; malformed records are warned and dropped; symlinks are ignored; interrupted restore leaves the record for a retry.
test_worktree.py adds coverage for identical rewrites, dropping empty patches, malformed JSON, build/gn between snapshot and restore, failed-push retry, build-after-failure, and interrupted restore.
Reviewed by Cursor Bugbot for commit 29dfe9dad368f80a99f531a0b6a175bc5fa965b5. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Keeps the mtimes of files worktree.sh sync pops and pushes back, so siso doesn't rebuild files that come back byte-identical from a pop and push.
Bug Fixes
- Before popping, sync snapshots the mtime and hash of each file the popped patches touch, plus a build marker that covers builds and
gn gen. - After the push, unmodified files get their old mtime back; changed files keep the new one.
- A retry after a failed push keeps the earlier record; one after a build drops it.
- The record now survives an interrupted restore, so a Ctrl-C or failed utime mid-loop can be retried to completion.
- Malformed records are dropped with a warning; symlinks are skipped.
- Verified on a Chromium 156 clone: dropping an empty patch 149 patches from the top kept mtimes on 459 of 461 files and cut the next build from at least 1,107 steps to 15.