On this page
#Changelog
#0.9.2
- Fix: macOS symlink compatibility. File paths are now resolved through symlinks before validation, fixing "is outside the repository" errors on macOS where
/varis a symlink to/private/var.
#0.9.1
- Fix: file paths from subdirectories.
safegit commitnow resolves relative file paths against the current working directory, not the repo root. Previously, runningsafegit commit -m "msg" -- file.txtfrom a subdirectory would fail with "does not exist and is not tracked."
#0.9.0
- Session trailers. Commits made inside Claude Code automatically get a
Claude-Code-Session-Idgit trailer for agent session traceability. Read from theCLAUDE_CODE_SESSION_IDenvironment variable. Amending from a different session preserves both session IDs as an audit trail. - Skip-worktree preservation.
SyncMainIndexnow saves and restores skip-worktree flags acrossgit read-tree. Previously, everysafegit commit, checkout, pull, merge, rebase, reset, bisect, cherry-pick, and revert cleared all skip-worktree flags.
#0.8.1
- **Standard
go installpath.** Moved main package fromcmd/safegit/to project root.go install github.com/smm-h/safegit@latestnow works without thecmd/safegitsuffix.
#0.8.0
- **
--flag=valuesyntax.** All commands now accept--flag=valuein addition to--flag value. Applies globally (e.g.,--config=/path) and to commit (-m="msg",-F=path,--branch=name), push, and rewrite-author flags. - **Per-command
--help.** Every command now responds to--help/-hwith flag descriptions and examples. Validation errors in rewrite-author include a usage hint. - **
--old-nameis now optional inrewrite-author.** Email-only rewrites work with just--old-email/--new-email. When both name and email flags are specified, matching uses AND logic (both must match). - **
--verboseoutput.**--verbosenow produces detailed output for commit (files, ref, tree, SHA), push (remote URL, hook results, retries), doctor (per-check timing), and rewrite-author (per-commit and per-ref details). - **Confirmation prompts for
rewrite-author.** Prompts before rewriting history and before force-pushing. Skipped with--force.--forcealso skips the dirty-tree check. - **Confirmation prompt for
doctor --uninstall.** Prompts before removing safegit from a repository. Skipped with--force. - Output improvements. "Parent-propagation only" replaced with "inherited (ancestors changed)". Singular/plural grammar fixed. Dry-run output includes email mapping when email flags are set.
- Annotated tag email rewriting. Tagger email is now rewritten alongside tagger name when
--old-email/--new-emailare specified.
#0.7.3
- Email rewriting.
safegit rewrite-authornow accepts--old-emailand--new-emailflags to rewrite author/committer emails alongside names. Supports email-only rewrites when--old-nameand--new-nameare the same. - Fix: graceful error when origin remote is missing.
--pushnow checks for theoriginremote before attempting to force-push, with a clear error message instead of a raw git error.
#0.7.2
- **Fix:
rewrite-authornow excludes stash and notes refs.**rev-list --allincludesrefs/stashandrefs/notes, which are not covered byupdateRefs. Replaced with explicit ref globs (refs/heads,refs/tags,refs/remotes) so only branch, tag, and remote tracking refs are walked and rewritten.
#0.7.1
- **Fix:
rewrite-authornow updates remote tracking refs.** Previously,refs/remotes/were walked during commit rewriting but not updated, causing the post-rewrite snapshot to see both old and new commits and failing verification with doubled commit counts.
#0.7.0
- **
rewrite-authorcommand.**safegit rewrite-author --old-name X --new-name Yrewrites author/committer names across all repository history using native Go git plumbing (no external dependencies like git-filter-repo). Includes a 13-check verification framework comparing pre/post-rewrite state: commit count, tag count, branch names, tag names, old name absence, commit messages, author dates, committer dates, author emails, tree hashes, parent topology, working tree cleanliness, and tag-to-message mapping. Supports--dry-runfor preview,--pushfor automatic force-push, annotated tag rewriting, merge commits, and multiple branches. Logged to oplog (not undoable). - Pre-commit hook support.
safegit commitnow runs.git/hooks/pre-commitwithGIT_INDEX_FILEpointing to the tmp index, so hooks see the correct staged files. Skipped with--force(matching git's--no-verify) and--dry-run. Previously, the plumbing-based pipeline bypassed git hooks entirely. - Directory deletion support.
git rm --cachednow retries with-rwhen the target is a tracked directory. Previously, committing a deleted directory failed with "not removing recursively without -r".
#0.6.2
Internal improvements.
#0.6.1
Internal improvements.
#0.6.0
#Removed
- **
initcommand.** Replaced by auto-initialization: any safegit command on an uninitialized repo creates.git/safegit/automatically. Usesafegit doctor --uninstallto remove safegit from a repo.
#0.5.0
#Removed
- **
wipcommand and all WIP snapshot functionality.**wip create,wip list,wip restoreare gone. Thewip-locks/directory is no longer created. Usesafegit commiton a temporary branch to save work-in-progress instead. - Submodule and Git LFS refusal check. Repos with
.gitmodulesorfilter=lfsare now fully supported. Both work correctly under concurrency (verified by experiment scripts). - **Placeholder
pre-pre-pushhook installation.** No-op hook is no longer installed automatically. Usesafegit hook installto add hooks.
#Added
- Auto-initialization. Running any safegit command on an uninitialized repo now creates
.git/safegit/automatically. A one-time message is printed to stderr.
#Fixed
- macOS ARM64 build.
doctor_darwin.gofailedgo veton ARM64 due to[]int8tostringconversion. Fixed by converting through[]byte.
#0.4.0
#CLI consolidation (24 -> 20 commands)
- **
amendandrewordmerged intocommit --amend.**safegit commit --amend -- filesreplacessafegit amend.safegit commit --amend -m "msg"with no files replacessafegit reword. Mirrors git's native interface. - **
unwipmerged intowip restore.**safegit wip restore <id>replacessafegit unwip <id>. All wip operations now live under one command. - **
gcmerged intodoctor --fix.**safegit doctor --fixreplacessafegit gc. Doctor already diagnosed the problems; now it can fix them too. - **
branchremoved.** Usegit branchdirectly. Zero concurrency safety was added by the wrapper.
#0.3.0
#Removed
- **
--format jsonflag and all JSON output.** Every command now produces only human-readable output. The JSON envelope infrastructure (emitJSON,jsonResponse,jsonError,--formatflag) is gone. 9 of 22 commands silently ignored--format jsonanyway (all guarded passthroughs), making the feature inconsistent. Agents already parse human output and git's native formats directly.
#0.2.0
#Removed commands
Seven commands dropped to reduce surface area and eliminate code that adds no concurrency safety:
- **
safegit status** -- JSON wrapper aroundgit status --porcelain. Agents can parse porcelain format directly; it's designed to be machine-readable. The JSON re-encoding was ~100 lines of parsing code that had to be maintained as git's output evolves, with no concurrency benefit. - **
safegit diff** -- JSON wrapper aroundgit diff. The hunk-splitting logic (splitDiffChunks) already had one panic bug. Agents already parse unified diff routinely, and git's native output is the source of truth. - **
safegit log** -- JSON wrapper aroundgit log. Git's own--formatflag already produces structured output. Marginal value over native git. - **
safegit show** -- JSON wrapper aroundgit show. Same rationale aslog. - **
safegit stash** -- Guarded passthrough, but the guard doesn't solve the real problem. In a multi-agent worktree,git stashcaptures all agents' uncommitted changes indiscriminately. The operation itself is fundamentally unsafe with concurrent agents. Usesafegit wipinstead (per-file, lock-protected). - **
safegit tag** -- Unguarded passthrough with zero safegit logic. Agents can usegit tagdirectly with no risk since tags don't affect the working tree or index. - **
safegit fetch** -- Unguarded passthrough with zero safegit logic. Fetch only updates remote-tracking refs, which is safe to do concurrently. Usegit fetchdirectly.
#0.1.6
- Fix:
safegit unwipnow refuses to restore when files were modified since the wip was created, preventing silent data loss from overwriting another agent's edits - Fix: platform-specific NFS detection refined into separate linux/darwin/windows files (replaces combined doctor_unix.go)
- Fix: goreleaser ldflags inject version string into release binaries
- Add Dockerfile for building safegit from source
#0.1.5
- Add CAS retry jitter (1-10ms random sleep) to break thundering-herd stampedes under heavy concurrency
- Prevent potential slice mutation in oplog.Append
#0.1.4
- Thread context.Context through all git helpers and callers (enables cancellation and clean shutdown)
- Signal handler cleans up lock files on SIGINT/SIGTERM (prevents 30s stale-lock timeout on macOS)
--branchnow errors if the target branch does not exist (prevents orphan branches from typos)- Config validation rejects zero values (consistent with
safegit configCLI behavior) - Coord guard diffs against HEAD directly instead of relying on the main index (eliminates false dirty-tree refusals)
#0.1.3
- Fix goreleaser config: add
main: ./cmd/safegitso release binaries build correctly - Drop Windows from build targets (Unix-only syscalls in lock, oplog, index, hooks packages)
- Fix push JSON output: hook failures now emit
ok: falsewith error details
#0.1.2
- Harden lock staleness: check hostname and /proc start-time to detect PID reuse across reboots
- Validate config values at load time (reject negatives and zero durations)
- Fix Windows build: extract NFS filesystem check into platform-specific files
- Fix
splitDiffChunkspanic on empty diff output - Fix
safegit hook run <name>now runs only the named hook, not all hooks - Fix amend on root commit gives a clear error instead of cryptic git output
- Fix JSON mode for diff/log/show: strip user-supplied --color/--format/--pretty flags
- Fix uninstall now cleans shared worktree lock directory
- Fix GC reports all removal errors instead of just the last one
- Fix amend/reword SyncMainIndex guarded for cross-branch operations
#0.1.1
#New features
safegit undoreverses the last commit, amend, or reword using the oplogsafegit amend --branchandsafegit reword --branchfor cross-branch operation- Passthrough for
stash,cherry-pick,revert(with coordination guards) andtag - Push oplog now records individual ref SHAs for auditing
- Ref locks shared across git worktrees via
git rev-parse --git-common-dir - Initial commit support:
safegit commitworks in empty repos with no prior commits - Version string auto-detected via
debug.ReadBuildInfoforgo installusers
#Bug fixes
- Cross-branch commit no longer clobbers the main
.git/index - Cross-branch amend/reword no longer clobbers the main
.git/index - Amend now checks wip-locks (previously bypassed the protection entirely)
- Reword retries on CAS miss instead of hard-failing
- Amend uses the target ref instead of literal
HEAD(fixes TOCTOU) - Zero-length lock files (from crashes) are now treated as stale and recovered
- Transient
git update-reflock failures are retried instead of hard-failing - Doctor no longer deletes orphan tmp dirs (reports only; use
gcto clean) - Broken symlinks detected correctly via
os.Lstatinstead ofos.Stat - Filenames containing colons no longer misidentified as hunk specs
- Push retry backoff reduced from 1s/4s/16s to 1s/2s/4s
- Detached HEAD produces a clear error message instead of an opaque git error
#Breaking changes
- Wip no longer reverts files in the working tree. Previously,
safegit wipwould snapshot files and revert them to HEAD. Now it only snapshots and creates wip-locks. This prevents clobbering another agent's uncommitted edits in a shared worktree. Users who need to revert files after wip should do so manually. - Wip commit message format changed from
files: a.txt, b.txt(comma-separated) to onefile: <path>line per file. This fixes parsing failures for filenames containing commas. Old wip commits using the legacy format are still supported for restore. safegit unwipno longer accepts--force(the clean-check it bypassed was removed)
#0.1.0
- Two-phase commit pipeline with per-invocation tmp indexes and CAS retry
- Amend and reword commands with CAS safety
- Wip snapshots via refs with per-file locking
- Coordination guard for tree-mutating operations (checkout, merge, rebase, reset, pull, bisect)
- Pre-pre-push hooks with timeout, process-group signaling, and hook-requested timeout override
- Push with retry logic and transport-error detection
- Doctor health checks: initialization, stale locks, orphan dirs, bypass detection, filesystem, hooks, unsupported features
- Garbage collection for orphan tmp dirs, wip-locks, and log rotation
- Structured JSON output (--format json) for all commands
- Hunk-level staging via file:hunk-spec syntax
- Config management with dot-separated keys
- Lock recovery via PID liveness detection
- Append-only JSONL operation log with atomic writes
- Cross-branch commit via --branch flag