Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Windows recovery-state readers do not permit delete sharing, so concurrent atomic replacement can fail and leave stale recovery data.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds atomic state persistence, separate operation/catalog locks, and resumable migration primitives for future shared worktree storage.
Changes:
- Implements cross-platform atomic file replacement.
- Adds migration conflict detection, backups, journaling, and recovery.
- Extends locking and stale-write protection with comprehensive tests.
| File | Description |
|---|---|
cmd/rebase.go |
Uses atomic rebase-state writes. |
internal/git/gitops_test.go |
Improves rebase failure diagnostics. |
internal/modify/state.go |
Uses atomic modify-state writes. |
internal/stack/atomic.go |
Implements shared atomic-write logic. |
internal/stack/atomic_unix.go |
Adds Unix publication and directory syncing. |
internal/stack/atomic_windows.go |
Adds Windows replacement and share-delete reads. |
internal/stack/atomic_windows_test.go |
Tests Windows reader-safe replacement. |
internal/stack/lock.go |
Separates catalog and operation locks. |
internal/stack/lock_test.go |
Tests locking and atomic publication. |
internal/stack/migration.go |
Implements resumable catalog migration. |
internal/stack/stack.go |
Integrates atomic saves and migration guards. |
internal/stack/stack_test.go |
Tests migration, conflicts, and recovery. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
4c4713f to
ed0be40
Compare
| "os" | ||
| "path/filepath" | ||
| "time" | ||
|
|
| target := StatePath(gitDir) | ||
| tmp := target + ".tmp" | ||
| if err := os.WriteFile(tmp, data, 0644); err != nil { | ||
| if err := stack.WriteAtomic(StatePath(gitDir), data); err != nil { | ||
| return fmt.Errorf("writing modify state: %w", err) | ||
| } | ||
| // Remove existing target before rename for Windows compatibility | ||
| // (os.Rename fails on Windows if the target already exists). | ||
| _ = os.Remove(target) | ||
| if err := os.Rename(tmp, target); err != nil { | ||
| _ = os.Remove(tmp) | ||
| return fmt.Errorf("committing modify state: %w", err) | ||
| } |
There was a problem hiding this comment.
This seems like the biggest core change in this PR right? If I am understanding this correctly we are now writing to a temp file before we replace the pre-existing one (in stack/atomic.go) instead of what appears to be deleting and then replacing the file in the deleted code?

Makes the files that record stacks and interrupted operations safer to save and read. It also prepares the move from separate tracking files in each checkout to one shared stack catalog, without losing existing stack definitions.
Functionality and user impact
Boundary: commands still use their existing catalog locations. #528 turns on shared storage and migration. This PR changes how files are saved, not branch contents or worktree layouts. Worktree support requires Git 2.36+ and does not create/remove worktrees or stash changes automatically.
Key areas to review
internal/stack/atomic.go:WriteAtomic/writeFileAtomicwrite and flush a temporary file before replacing the saved file. Check that failures are reported and temporary files are cleaned up.atomic_unix.goandatomic_windows.go: handle replacement on each platform, including Windows readers that still have the old file open. Creating a backup must never overwrite an existing backup.internal/stack/stack.go:SaveandcheckStaleprevent overwriting changes made since loading.SaveNonBlockinglets an optional metadata refresh skip saving when another writer is active.internal/stack/lock.go:Lockis held only while saving the catalog.LockOperationcan cover a whole command, including branch changes and the final save. Keeping them separate lets the command save without trying to acquire a lock it already holds.internal/stack/migration.go:MigrateLegacyState,mergeMigrationCatalogs, andcheckMigrationSnapshotdecide which definitions can be combined. Follow how original data is saved before the new catalog replaces it, and how interrupted work resumes.Related issues
gh stack rebasesilently succeeds whengit rebase --ontofails to start (in branch checked out in another worktree) #35Part 2 of the 4-PR split of #520