safegit

command module
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 34 Imported by: 0

README

safegit

Go CLI wrapper around git for safe concurrent multi-agent use.

The problem

When multiple AI agent sessions share a single git repository, they race on .git/index. Two agents staging files at the same time produce mixed commits -- files from one agent leak into another's commit, or writes are silently lost. Standard git has no built-in isolation for this scenario.

The solution

safegit wraps git plumbing commands behind a two-phase commit pipeline that keeps every invocation isolated. Per-invocation temporary index files prevent staging races. Ref updates use per-ref locks with compare-and-swap (CAS) retry, so concurrent commits to the same branch serialize correctly. An append-only operation log records every mutation. The output is standard git commits -- teammates, CI, and code review tools see nothing unusual.

Install

From source (requires Go 1.24+):

go install github.com/smm-h/safegit@latest

Pre-built binaries are available on GitHub Releases via goreleaser.

Quick start

cd your-repo
safegit commit -m "add feature X" -- src/foo.go src/bar.go
safegit push

safegit auto-initializes on first use (creates .git/safegit/). Use safegit doctor --uninstall to remove safegit from a repo.

Commands

Command Description
bisect binary search through commits to find a bug, with safety guards
checkout checkout a branch or ref with working-tree safety guards
cherry-pick cherry-pick one or more commits onto HEAD with safety guards
commit stage and commit specified files in a single atomic operation
doctor run diagnostic health checks on the repository and optionally repair issues
merge merge a branch into HEAD with working-tree safety guards
pull fetch from remote and merge, defaulting to fast-forward-only mode
push push refs to remote with pre-pre-push hooks and automatic retry
rebase rebase current branch onto upstream with safety guards
reset reset HEAD with guards that prevent accidental --hard data loss
revert revert one or more commits creating inverse patches, with safety guards
scan search git history for regex pattern matches across all objects and working tree files, scanning blobs, commit messages, tag annotations, and trailers with optional scope filtering and commit range selection
undo reverse the last commit, amend, or reword operation using the oplog
unlock release a stale .lock file left behind by a crashed git process
version print safegit version, Go runtime version, and git version
author audit and rewrite commit author/committer identity — list all identities, check against expected values, and rewrite name or email across history
author check check that all commits use the expected author and committer identity by scanning every commit in the repository history, reporting any deviations with the exact commit hashes and mismatched fields, and suggesting the corresponding safegit author rewrite command to fix each deviation found
author list list all distinct author and committer identities across the entire commit history, showing name, email, role, and commit count for each unique identity — useful for auditing repositories with multiple contributors or detecting unwanted identity variations such as typos, old email addresses, or bot accounts that should be consolidated before a rewrite
author rewrite rewrite author and committer name or email across all commit history using git filter-branch style rewriting, replacing every occurrence of the old identity with the new one in both author and committer fields while preserving timestamps, commit messages, tree contents, and parent relationships so the rewritten history is otherwise identical to the original
backup push, list, and restore per-branch history backups held in the tool-owned refs/backups namespace on a remote, so uncommitted-to-the-world work survives a lost machine without ever touching refs/heads
backup backup push the current branch to its backup slot refs/backups/ on the remote, after fetching that slot and refusing when it holds commits your history does not contain; the push is pinned with --force-with-lease to the exact SHA that was just observed (or to "this ref must not exist" for a first backup), so a concurrent backup from another machine is rejected rather than clobbered; plain git equivalent: git push --force-with-lease=refs/backups/: HEAD:refs/backups/
backup list list every backup slot present on the remote with the branch name and the commit each slot points at, so you can see which branches are backed up from which machine before restoring one; plain git equivalent: git ls-remote 'refs/backups/*'
backup restore fetch the current branch's backup slot from the remote and fast-forward the branch onto it, refusing when the local branch carries commits the backup does not contain so no local work is ever discarded; plain git equivalent: git fetch refs/backups/ && git merge --ff-only FETCH_HEAD
config show, get, or set safegit configuration key-value pairs
config get get the current value of a single configuration key from the .git/safegit/config.json file, printing the raw value to stdout so it can be captured by scripts or used in automation pipelines
config set set a configuration key to a new value in the .git/safegit/config.json file, creating the file if it does not exist yet, and persisting the change for all future safegit invocations in this repository
config show show all configuration values currently in effect for this repository, including built-in defaults and any user overrides from the .git/safegit/config.json file, printed as key-value pairs to stdout for inspection and debugging purposes
hook manage pre-pre-push hook scripts that run before every push
hook install install a pre-pre-push hook by copying a script file into the .git/safegit/hooks directory, making it executable, and registering it so that safegit push will run it before any network I/O occurs
hook list list all pre-pre-push hooks currently installed in the .git/safegit/hooks directory, showing each hook name, file path, and whether it is executable, so you can audit which checks run before every push
hook run run all installed pre-pre-push hooks (or a single named hook) immediately without performing an actual push, so you can verify that all configured hooks pass before committing to a real push operation
scrub surgically rewrite git history to remove or replace sensitive content using 4 subcommands (file, match, run, verify) that operate on all commits, trees, and blobs in the repository
scrub file replace or remove a specific file across all commits in the repository history, rewriting each affected commit tree to either substitute the file contents with a sanitized version or delete the file entirely from every historical snapshot
scrub match replace all occurrences of a regex pattern across every blob in the repository history, rewriting commit trees to substitute matched text with a replacement string so that sensitive values like secrets and credentials are permanently removed from all historical snapshots
scrub run execute a multi-operation scrub recipe from a TOML file, applying all pattern replacements and file removals across history in a single coordinated pass with topological commit ordering, overlap detection between operations, and automatic verification that no matched content survives in the rewritten object store — use --diff to preview all changes as unified diffs before committing to the rewrite
scrub verify check all scrub policies defined in the repository configuration to confirm that previously scrubbed secrets and sensitive patterns remain completely absent from every object in the git object store, scanning blobs, commit messages, and tag annotations and reporting detailed per-policy pass or fail results with match locations for any violations found

Tree-mutating commands (checkout, pull, merge, rebase, reset, bisect, cherry-pick, revert) are passed through with coordination guards.

How it works

The commit pipeline has two phases. Phase A (parallel-safe) creates a temporary index, stages the requested files into it, and builds the tree object -- all without touching the shared .git/index. Phase B acquires a per-ref lock, reads the current tip, creates the commit with that parent, and updates the ref using CAS. If the ref moved between read and write, Phase B retries from the new tip (re-parenting the commit) with random jitter to avoid thundering-herd stampedes under heavy concurrency.

See docs/architecture.md for the full architecture specification.

Configuration

Run safegit config to view all settings, or safegit config <key> <value> to change one.

Key Default Description
commit.casMaxAttempts 5 Max CAS retry attempts for ref updates
lock.acquireTimeoutSeconds 30 Timeout waiting for a per-ref lock
hooks.preprepush.timeoutSeconds 1800 Timeout for pre-pre-push hook execution
push.retryAttempts 3 Number of push retry attempts
log.maxSizeMB 100 Max operation log size before rotation

Configuration is stored in .git/safegit/config.json. Remove the entire .git/safegit/ directory to return to vanilla git.

Known limitations

  • Same-machine concurrency only. Lock staleness detection uses PID liveness checks and hostname comparison. On network filesystems (NFS, CIFS), safegit doctor warns about reduced lock atomicity guarantees. Cross-machine lock reclaim is refused when the hostname doesn't match.
  • PID reuse. On Linux, safegit compares process start time against lock creation time via /proc to detect PID reuse. On other platforms, a reused PID keeps an orphan lock looking alive, and safegit unlock <ref> refuses to clear a lock whose holder is alive, so such a lock has to be removed by hand from .git/safegit/locks/. Where the holder really is gone, safegit unlock refs/heads/main clears the lock.
  • Linux and macOS only. Windows is not supported (Unix-only syscalls for locking, signals, process management). WSL (Windows Subsystem for Linux) works since it runs the Linux binary natively.

License

MIT

Documentation

Overview

Package main is the entry point for the safegit CLI, a concurrency-safe git wrapper that isolates commits via per-invocation temporary indexes.

Directories

Path Synopsis
internal
commit
Amend and Reword implement tip-commit rewriting with CAS safety.
Amend and Reword implement tip-commit rewriting with CAS safety.
coord
Package coord implements the coordination layer that prevents concurrent agents from corrupting the working tree by guarding tree-mutating operations.
Package coord implements the coordination layer that prevents concurrent agents from corrupting the working tree by guarding tree-mutating operations.
filelock
Package filelock provides platform-safe file locking for append operations.
Package filelock provides platform-safe file locking for append operations.
git
Package git wraps os/exec calls to the git binary and is the sole interface through which safegit interacts with git plumbing commands.
Package git wraps os/exec calls to the git binary and is the sole interface through which safegit interacts with git plumbing commands.
hooks
Package hooks discovers and executes pre-pre-push hooks that run before any network I/O, solving the SSH timeout problem when checks are long-running.
Package hooks discovers and executes pre-pre-push hooks that run before any network I/O, solving the SSH timeout problem when checks are long-running.
index
Package index manages per-invocation temporary git indexes so each safegit invocation stages into its own index seeded from HEAD, avoiding contention.
Package index manages per-invocation temporary git indexes so each safegit invocation stages into its own index seeded from HEAD, avoiding contention.
lock
Package lock provides ref-lock primitives for concurrent ref updates using O_CREAT|O_EXCL for atomic lock file creation and exponential backoff polling.
Package lock provides ref-lock primitives for concurrent ref updates using O_CREAT|O_EXCL for atomic lock file creation and exponential backoff polling.
oplog
Package oplog implements the append-only JSONL operation log that records every mutating operation for undo support and audit trail purposes.
Package oplog implements the append-only JSONL operation log that records every mutating operation for undo support and audit trail purposes.
procutil
Package procutil provides cross-platform process liveness checks.
Package procutil provides cross-platform process liveness checks.
repo
Package repo manages the .git/safegit/ data directory including initialization, configuration loading, validation, and path helpers for all state files.
Package repo manages the .git/safegit/ data directory including initialization, configuration loading, validation, and path helpers for all state files.
scan
Package scan iterates all git objects (reachable and unreachable) and matches patterns against their textual content for history scrubbing.
Package scan iterates all git objects (reachable and unreachable) and matches patterns against their textual content for history scrubbing.
stage
Package stage implements hunk-level staging against temporary indexes by parsing unified diffs and applying selective patches for partial-file commits.
Package stage implements hunk-level staging against temporary indexes by parsing unified diffs and applying selective patches for partial-file commits.
submodule
Package submodule enumerates initialized and deinitialized git submodules, detects parent repos, checks for nesting, and resolves paths through symlinks.
Package submodule enumerates initialized and deinitialized git submodules, detects parent repos, checks for nesting, and resolves paths through symlinks.
testutil
Package testutil provides shared test helpers for creating temporary git repos, used across internal/*_test.go packages to avoid duplicating boilerplate.
Package testutil provides shared test helpers for creating temporary git repos, used across internal/*_test.go packages to avoid duplicating boilerplate.
trailer
Package trailer injects git trailers (key-value metadata lines) into commit messages for AI agent traceability and session attribution.
Package trailer injects git trailers (key-value metadata lines) into commit messages for AI agent traceability and session attribution.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL