feat(snapshots): one-command restore recovery#10
Merged
Conversation
… onboarding drop-off) `git checkout <snapshot-ref> -- .` was the only documented recovery — a git-native step that a product trier who doesn't understand snapshot refs abandons on (adversarial review, fugu drop-off #3). Replace it with: agentic-git snapshots restore [<ref>] [--repo <path>] [--yes] [--staged] Design settled against an adversarial design pass: - Non-destructive: writes the snapshot's paths back; NEVER deletes files created after it (this is a *recovery* tool, not a tree rollback). - Lands UNSTAGED by default (a user recovering files does not expect them silently staged into the next commit); `--staged` opts into the classic `checkout -- .` index side effect. - Fail-CLOSED pre-restore snapshot: restore overwrites the working tree, so it snapshots current state first (undo target) and ABORTS if that fails — unlike `maybe_snapshot`, which fails open because it must never block the git op it protects. This is our own command; no reason to overwrite unsaved work with the net down. - No-ref resolution refuses to guess: exactly one snapshot → use it; several → exit 2 and list candidates unless `--yes` (then the genuinely-newest, tie-broken by ref `<seq>` since dates are only second-granular). - Only restores from `refs/agentic-git/snapshots/…` — never an arbitrary branch/tag. Paths resolved via `diff --name-status -z` (spaces/newlines safe; `A` entries = newer files, excluded). 6 end-to-end tests (real shim snapshot + real CLI restore): unstaged recovery, `--staged`, ambiguous+`--yes`-newest, bad-ref reject, no-snapshots, and a non-destructive undo round-trip. README updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…BIG on large snapshots) fugu's adversarial review (PR #10) repro'd a real blocker: a `clean -fd` snapshot of 60,000 long filenames restored ZERO files — `git checkout` received every changed path as argv and died with `Argument list too long` (E2BIG) before touching the tree. One-command recovery was unusable for exactly the case it's for: large generated trees / vendored dirs. Fix: feed pathspecs to checkout AND reset over stdin via `--pathspec-from-file=- --pathspec-file-nul`. No argv limit; one invocation handles any count. Verified against git 2.39. Bonus: paths now stay RAW BYTES end-to-end (diff `-z` → `Vec<u8>` → NUL-joined stdin), dropping the lossy `String::from_utf8_lossy` conversion — a non-UTF-8 filename round-trips unchanged (fugu's secondary concern). Regression test R7: 5000 files with 230-char names (>1 MB of pathspec, past macOS ARG_MAX) lost and fully recovered in one command. Full workspace suite green (23 snapshot tests), clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… git magic) fugu re-review (PR #10): a legal filename that begins with git's pathspec sigil — `:(glob)literal.txt`, `:/foo`, `:x` — was snapshotted faithfully but could NOT be recovered: `git checkout` parsed the fed pathspec as a magic expression (`pathspec ... did not match`) instead of a literal path. The recovery promise broke for an entire class of legal names. Fix: set `GIT_LITERAL_PATHSPECS=1` on the pathspec-stdin checkout AND reset, so every fed path is treated literally, never as magic. Verified against git 2.39 (repro recovers `:(glob)literal.txt` byte-for-byte). This closes the weird-filename class comprehensively: bulk/E2BIG (stdin), spaces+newlines (-z/NUL), non-UTF-8 bytes (raw Vec<u8>), and now magic sigils (literal pathspecs). Regression test R8: a `:(glob)…`-named file lost and recovered. 24 snapshot tests green, clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
suzuke
added a commit
that referenced
this pull request
Jul 5, 2026
Bumps the shim to 0.2.0 for the one-command recovery (`snapshots restore`, #10) and run-first onboarding (#11). agentic-git-core stays 0.1.0 — the contract crate is unchanged, so the two versions move independently (the shim's `version` is now explicit rather than inherited from the workspace). Publishing: the release workflow publishes core first (0.1.0, already on crates.io → tolerated) then the shim 0.2.0. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
agentic-git snapshots restore [<ref>] [--repo <path>] [--yes] [--staged]— the one-command form of the previously-documented manualgit checkout <snapshot-ref> -- ..Why
Adversarial product review (fugu) flagged the git-native restore as a top-3 onboarding drop-off: a product trier who doesn't understand snapshot refs abandons at the recovery step. This makes recovery a single command that needs no git internals.
Design (settled against an adversarial design pass)
--stagedopts into the classic index side effect.maybe_snapshot, which fails open because it must never block the git op it protects — this is our own command.)--yes(then the genuinely-newest, tie-broken by ref<seq>since forced dates are only second-granular).refs/agentic-git/snapshots/…, never an arbitrary branch/tag.Path resolution via
diff --name-status -z(spaces/newlines safe;A= newer files, excluded).Tests
6 end-to-end tests (real shim creates the snapshot, real CLI restores):
restore_cli_recovers_tracked_and_untracked_unstaged,_staged_flag_leaves_index_staged,_refuses_to_guess_then_yes_takes_newest,_rejects_non_snapshot_ref,_no_snapshots_exit_1,_is_nondestructive_and_undoable(undo round-trip via the pre-restore snapshot).Full workspace suite green (14 binaries),
clippy --all-targets -D warningsclean.🤖 Generated with Claude Code