Storage and Recovery Refs¶
Git-stage-batch stores durable batch state under refs/git-stage-batch/ and
worktree-local session scratch files below the worktree's Git directory.
Batch metadata authority and crash residue¶
Batch content and state refs are authoritative. A file-backed
batches/<name>/metadata.json is only a compatibility/publication staging file;
when a state ref exists, normal reads deliberately ignore that file. If a process
stops between writing metadata and publishing or cleaning refs, run
git-stage-batch validate --porcelain to classify the residue as redundant,
stale, conflicting, orphaned, legacy-compatible, or invalid. Stale, conflicting,
or unknown-schema files should be preserved for inspection rather than published
as live batch state.
Object identifiers are not reachability roots¶
Session manifests and abort snapshots serialize Git object IDs so state can be restored later. An object ID written into JSON is not an edge in Git's object graph. If the batch ref that previously named that object moves or is deleted, reflog expiration followed by garbage collection may otherwise prune it.
During an active session, git-stage-batch creates internal refs below
refs/git-stage-batch/session/anchors/. Each ref names a commit, tree, or blob
that an undo, redo, or abort operation promises to restore. Checkpoint stack
refs describe undo and redo order; anchor refs provide reachability. Those are
separate responsibilities.
Gitlink entries name commits in a submodule's separate object database. They cannot be rooted by refs in the superproject; their availability remains the responsibility of the nested repository.
Anchors are created before a checkpoint is published or a command can mutate
the protected refs. They remain for the session lifetime so every checkpoint
still present on either stack remains recoverable. A successful stop or
abort removes the complete anchor namespace. Reclaiming a stale linked-
worktree owner also removes the abandoned session's anchors.
Older checkpoints may not contain recorded anchor metadata. Git-stage-batch attempts to restore them while their serialized objects remain available. If an object has already been pruned, restoration stops before mutation and reports the missing recovery object rather than partially applying the checkpoint.
Scoped undo checkpoints¶
Each mutating command declares the repository paths it reads or writes. Undo manifests record only those worktree paths and index entries. Once an operation finishes, its checkpoint is also reduced to the session files, batch metadata, and refs that actually changed. Unrelated dirty or staged files and unrelated application metadata are not copied into the finalized checkpoint. Regular files in one scope share bulk index, HEAD, and object writes, so checkpoint process count does not grow with unrelated worktree dirtiness.
Undo and redo restore scoped index entries individually instead of replacing
the complete index, and restore only changed application-state paths and batch
refs. A later change outside the command scope remains in place. Changes to a
scoped path or ref still cause the default safety refusal, and --force
overwrites only state owned by that checkpoint. Legacy whole-index and
whole-state checkpoints remain readable for sessions created by older
versions.
Atomic file permissions¶
Git-stage-batch writes session metadata, recovery manifests, batch
compatibility metadata as private application state. Newly created state files
use mode 0600, including when the process has a permissive umask. Rewriting
private state also restores that restrictive mode.
Repository-owned files use a separate atomic-write policy. Updates to
.gitignore, .git/info/exclude, and previously installed assistant assets
preserve the existing file's permission bits and ownership where the platform
allows it. New repository files use the conventional mode 0644. If ownership
cannot be restored, replacement permissions are narrowed rather than granting
access through a different group.
Atomic replacement writes and syncs a temporary file in the destination directory before renaming it over the target, then syncs the directory where the platform supports that operation. A failure before replacement leaves the old file complete. Symlink targets are never followed or silently replaced; the command stops with recovery guidance so callers can update the intended target explicitly.
Diagnostic journals¶
Diagnostic journaling is disabled by default, so ordinary commands do not
inspect the Python stack, serialize journal entries, open journal files, or run
extra Git queries for diagnostics. Set GIT_STAGE_BATCH_JOURNAL to one of the
following levels when investigating a problem:
metadata-onlyrecords structured operation names, stable source IDs, object IDs, modes, sizes, and hashed path identifiers.verboseadds a bounded stack for each event. Error events also include a bounded stack at the metadata level.content-debugadditionally records raw paths, Git command output, and short content previews. This level can expose repository content and should only be enabled for a limited reproduction.disabledturns journaling off explicitly.
The historical GIT_STAGE_BATCH_DEBUG switch selects verbose for
compatibility; it does not enable raw content capture.
Journal files are stored under
$XDG_STATE_HOME/git-stage-batch/journals/, or
~/.local/state/git-stage-batch/journals/ when XDG_STATE_HOME is unset. The
filename contains a stable hash of the repository identity rather than its
path. The journal directory uses mode 0700 and files use mode 0600.
GIT_STAGE_BATCH_JOURNAL_PATH can override the destination for a controlled
debugging environment.
Entries are queued in a bounded process buffer. The buffer flushes when it reaches 64 KiB, at each interactive action boundary, and when a CLI command exits. A process terminated without normal cleanup can lose entries since the last boundary; journaling never changes the durability of repository state. Writers use a per-journal lock so concurrent processes append complete JSON lines.
The active file rotates at 5 MiB, retains at most three rotated files, and
expires journal files after 30 days. Use GIT_STAGE_BATCH_JOURNAL_MAX_BYTES
and GIT_STAGE_BATCH_JOURNAL_RETENTION_DAYS to adjust those limits. Run
git-stage-batch journal for a content-free summary,
git-stage-batch journal --path to locate the file, or
git-stage-batch journal --purge to remove it. Add --all to purge data for
all repositories. The disabled/event-heavy paths can be compared with
scripts/benchmark_journal.py from a source checkout.
Batch metadata schema¶
Authoritative batch.json records use a versioned schema. Schema version 1
stores an opaque revision identifier, batch identity, timestamps, baseline and
content object IDs, and validated per-path metadata. Writers emit only the
canonical current schema, while readers migrate the historical unversioned
shape in memory before exposing it to batch operations.
An unversioned file-backed record is copied to metadata.v0.json before its
first durable rewrite. Successful publication stores the canonical metadata in
the state ref, whose parent retains the previous state-ref version, and removes
the compatibility directory. Failed publication leaves the recovery copy for
inspection. Metadata from a newer unsupported schema is never rewritten.
Run git-stage-batch validate to validate every batch without changing it. The
command checks schema compatibility, object IDs, content-ref agreement, and
reports whether a legacy record would be migrated. Use --porcelain for a
stable JSON report suitable for support tooling.
Attribution working set¶
During hunk review, batch ownership is indexed by canonical file path for the current diff scan. Files without claims skip traversal across unrelated batch metadata. For a claimed file, source and deletion objects are requested once per role through bounded Git batch readers. Refspecs are resolved before content is loaded, so canonical state refs and legacy fallbacks that name the same blob share one source read and one line mapping. Deletion payloads are reduced to fingerprints as they stream, and normalized presence claims are computed once per batch/file key.
Attribution processes one file and one unique source mapping at a time. It retains compact fingerprints and result units for the file, but releases each source payload and mapping before opening the next source. Traversal is sorted by batch name so optimization and metadata insertion order cannot alter ownership arbitration. Missing deletion objects and objects of the wrong Git type are ignored conservatively rather than hiding an unverified change.