Skip to content

feat(git): per-worktree undo journal for ref moves (DAN-87) - #156

Merged
danielss-dev merged 1 commit into
mainfrom
developements/git-undo-journal-a431
Oct 19, 2026
Merged

danielss-dev merged 1 commit into
mainfrom
developements/git-undo-journal-a431

Conversation

@danielss-dev

@danielss-dev danielss-dev commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Linear: https://linear.app/daniels-dev/issue/DAN-87

First slice of multi-step undo/redo for Git actions. Each worktree stores strand-journal.json in its own git dir and records the before/after branch tips, HEAD, and index tree for ref-moving operations. Undo Git Action and Redo Git Action are separate from text undo: they are in the Edit menu (no native accelerator) and the command palette, and Mod+Z / Mod+Shift+Z run only while the commit graph or Local Changes has focus. A success toast names the action. Git Action History lists both stacks; Enter on a row walks the stack through that action.

Undo refuses when a recorded ref no longer points at its after-target. A hard reset, checkout, merge, rebase, or cherry-pick stashes tracked work first when the worktree or index would otherwise be overwritten (Safety: before …), matching reset. Pushes are not journaled, and undoing a later local commit does not move the remote.

Left out of this slice

  • Push, fetch, pull. A push has already left the machine. Reversing it would be an explicit force-with-lease, which this slice does not do.
  • Discard and stash. Those need working-tree and index snapshots, not just ref targets.
  • Conflicted merge, rebase, and cherry-pick. They stay on Abort / Continue. A clean sequencer op is journaled; a conflicted one is not.
  • Continue / Abort. They belong to the paused sequencer, not a new journal entry.
  • Revert. Not in this slice's op list. It can be added the same way as cherry-pick once we want it on the stack.
  • Sparse checkouts and partial clones. Capturing the index with git write-tree can expand a sparse index or fault missing blobs, so those repos skip the journal.
  • Tags, upstream config, and hosting-provider actions (PR merge and the like). They are not local branch/HEAD moves.

Interactive rebase is included: it uses the same rebase journal entry as git rebase.

Manual checks

Windows:

  • With the commit graph focused, Ctrl+Z undoes the latest git action and the toast names it. Ctrl+Shift+Z redoes it.
  • The same shortcut works from Local Changes when focus is on the file list, and does nothing to git when focus is in the commit subject or body (text undo still works).
  • Ctrl+Z in the Work editor still undoes typing.
  • Edit → Undo Git Action works from the menu. The menu item has no accelerator, so Ctrl+Z in a text field is not stolen.

macOS (nativeMenuPreemptsKeydown is true; predefined Edit → Undo/Redo own ⌘Z):

  • Confirm whether ⌘Z with the graph or Local Changes focused reaches Git undo, or is swallowed by the predefined Undo item before the webview. Menu click and the command palette must work either way.
  • Confirm ⌘Z / ⌘⇧Z in the commit message box and the Work editor still undo and redo text.
  • Edit → Undo Git Action and Redo Git Action have no accelerator on purpose. Do not add one.

Proof

  • cargo test -p strand-core: 260 lib tests plus clone/sparse integration tests, all passed. Journal coverage is journal::tests (15): round-trip for commit, amend, soft/mixed/hard reset, branch create/delete/rename, checkout, merge, rebase, interactive rebase, and cherry-pick; external ref move is refused and names refs/heads/main; deleted-branch undo restores the same SHA; rebase undo restores the pre-rebase tip; the journal file survives Repo::discover and is per worktree; push is not an entry and undo does not move the bare remote.
  • cargo clippy -p strand-core -p strand-tauri -p strand-ops -p strand-headless -p strand-azdo-protocol -p strand-azdo -- -D warnings passed.
  • pnpm --filter ./ui exec tsc --noEmit passed.
  • pnpm --filter ./ui test: 627 passed, including gitUndoBindingAllowed (graph and Local Changes allow the shortcut; an editable inside Local Changes, and the Work editor, do not) and the check that undo-git is not a native-menu command.

Review fixes

  • Undo and redo refuse before writing anything when a branch they would move, delete, or check out is checked out in another worktree.
  • Undo and redo refuse when moving a branch would drop a commit its upstream (or a same-named remote-tracking branch) already has. push_is_not_journaled_and_undo_refuses_to_drop_pushed_commits asserts the refusal, and that the local and remote refs are unchanged.
  • Each journal load → modify → save holds strand-journal.json.lock. Saves go to a unique temp file, then rename (no remove first).
  • Staged-only changes count as dirty. After the forced checkout, the restored staged paths go back on disk, so undoing a checkout that carried a staged change keeps it (and a safety stash is made).
  • Remove from history: drop_git_action and repo_drop_git_action. After a refused undo or redo, the history dialog offers to remove that stale top entry.
  • run_git's doc comment is reattached.
  • Round-trip tests assert that redo matches the post-op snapshot, including rename.
  • New tests: other-worktree refusal, carried staged change, stale-entry drop, and concurrent writers keeping every entry.
  • Round 2:
    • runGitAction drops a call while one is in flight, and the undo/redo shortcut ignores key auto-repeat.
    • checkout_staged_paths passes paths to checkout-index -z --stdin, not argv.
    • Undoing a checkout that carried a git rm no longer leaves a stray untracked copy. The re-created file is removed only if it is still the blob the checkout wrote. A git rm --cached file stays (the collision guard refuses that undo).
    • New tests: 2,000 long staged paths, a staged git rm, and a git rm --cached.

Merge order 157 → 153 → 155 → 154 → 156 → 158: this PR must wrap #155's new rebase signatures and #154's amend_into in the journal. #158 needs this PR's Commits.tsx attribute.

Open in Web Open in Cursor 

Squashed for the rebase onto #155 and #154: journal, worktree and pushed-commit
guards, stale-entry removal, staged-path stdin, overlap guards.
@danielss-dev
danielss-dev marked this pull request as ready for review October 19, 2026 15:12
@danielss-dev
danielss-dev force-pushed the developements/git-undo-journal-a431 branch from 334a9d8 to 2cfc040 Compare October 19, 2026 15:12
@danielss-dev
danielss-dev merged commit 895ecfa into main Oct 19, 2026
6 checks passed
danielss-dev pushed a commit that referenced this pull request Oct 19, 2026
Squashed for the rebase onto #155, #154 and #156. Drag-rebase now opens the
same RebaseOntoDialog as the Rebase menu (with the --update-refs choice).
danielss-dev added a commit that referenced this pull request Oct 19, 2026
…#158)

Squashed for the rebase onto #155, #154 and #156. Drag-rebase now opens the
same RebaseOntoDialog as the Rebase menu (with the --update-refs choice).

Co-authored-by: Grok Bot <grokbot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants