Skip to content
safegit
Edit
On this page

Command safegit wraps git, giving each commit its own temporary index and retrying ref updates on conflict, so concurrent agents share one repository.

#safegit

#.

Command safegit wraps git, giving each commit its own temporary index and retrying ref updates on conflict, so concurrent agents share one repository.

#IntentUnset

Go go
const IntentUnset IntentKind = iota

IntentUnset is the zero value and is always an error: a rewrite that reaches Finalize without declaring what it meant to do cannot be verified, and silently skipping the verification is exactly the hole the declaration exists to close.

#IntentPerPath

Go go
const IntentPerPath

IntentPerPath means the rewrite declared, per commit, which paths and which messages it changes. Every scrub carries this.

#IntentIdentityOnly

Go go
const IntentIdentityOnly

IntentIdentityOnly means the rewrite changes commit IDENTITY headers (author, committer, tagger) rather than content, so there is no per-path expectation to check -- but every tree must still come through byte-identical, and a message may change only where the walk declared it rewrote an identity-bearing trailer. author rewrite carries this.

#ScrubVerifyPatternResult

Go go
type ScrubVerifyPatternResult struct

ScrubVerifyPatternResult is the per-pattern result for JSON output.

#ScrubVerifyResult

Go go
type ScrubVerifyResult struct

ScrubVerifyResult is the top-level JSON output for scrub verify.

#Recipe

Go go
type Recipe struct

Recipe is the raw TOML schema for a scrub recipe file.

#RecipeOperation

Go go
type RecipeOperation struct

RecipeOperation is a single operation within a recipe.

#ParsedRecipe

Go go
type ParsedRecipe struct

ParsedRecipe is the validated, compiled form of a Recipe.

#RefUpdatePlan

Go go
type RefUpdatePlan struct

RefUpdatePlan is everything a rewrite will do to refs, computed before any of it happens. Splitting the plan from its application is what lets Tier A verification run on the finished object graph while the repository still points at the old one: every object the plan needs exists once the plan is built, and not one ref has moved.

#RewriteAuthorResult

Go go
type RewriteAuthorResult struct

RewriteAuthorResult is what author rewrite reports -- in both modes and in both renderings. There is no separate dry-run struct: the preview's counts and the execution's counts are members of one result, each present exactly when it was measured.

#IntendedChange

Go go
type IntendedChange struct

IntendedChange is what a rewrite operation DECIDED to change in one commit, recorded while the decision was made rather than read back off the result: the repo-relative paths whose blob (or gitlink) the operation replaced, and whether it rewrote the commit message.

Tier A verification diffs the old and new commit and refuses the whole rewrite when the two disagree. That is the preservation property: a rewrite may change what it said it would change, and nothing else.

#IntentKind

Go go
type IntentKind int

IntentKind says what KIND of expectation a rewrite carries, because the two answers a verifier can act on are different questions -- and because the zero value must be neither of them.

#RewriteIntent

Go go
type RewriteIntent struct

RewriteIntent is the declaration a rewrite hands to Finalize.

#RewriteMapStart

Go go
type RewriteMapStart struct

RewriteMapStart is the phase-"start" record.

#RewriteMapRefs

Go go
type RewriteMapRefs struct

RewriteMapRefs is the phase-"refs" record.

#RewriteMapComplete

Go go
type RewriteMapComplete struct

RewriteMapComplete is the phase-"complete" record.

#TierAFunc

Go go
type TierAFunc func(ctx context.Context, plan *RefUpdatePlan) error

TierAFunc is a command's own pre-refs verification. It runs while the rewritten commits are still unreachable objects, and it receives the ref update plan so it can inspect exactly what is about to become the repository's history -- the new commit tips and the new tag objects -- none of which any ref points at yet. A non-nil error refuses the whole rewrite.

#TierBFunc

Go go
type TierBFunc func(ctx context.Context) error

TierBFunc is a command's own post-cleanup verification. It runs after the refs have moved and the object store has been swept, so its findings cannot undo anything: a non-nil error is REPORTED and turns the command's exit code nonzero, with the rewrite standing.

#RewriteHooks

Go go
type RewriteHooks struct

RewriteHooks are the two verification hooks and the tag-annotation transform a rewrite hands to Finalize. Every field may be nil except where a command's own contract requires it.

#RewriteResult

Go go
type RewriteResult struct

RewriteResult collects the outputs of a history rewrite so that Finalize can execute the shared post-rewrite pipeline (verification, ref updates, cleanup, oplog, push hint).

#ScanResult

Go go
type ScanResult struct

ScanResult is the JSON output for safegit scan.

#ScanMatchJSON

Go go
type ScanMatchJSON struct

ScanMatchJSON is a single match in JSON output.

#ScrubFileResult

Go go
type ScrubFileResult struct

ScrubFileResult is what scrub file reports -- in both modes and in both renderings. There is no separate dry-run struct: a preview and an execution answer the same questions about the same rewrite, and every figure here is the one the human summary prints. The fields only an executed rewrite can know are pointers or omitempty, so a preview omits them rather than publishing a zero that reads as a fact.

#TagBodyTransformFunc

Go go
type TagBodyTransformFunc func(refname, header, body string) (newBody string, err error)

TagBodyTransformFunc transforms the body of an annotated tag. It receives the tag's refname, full header text, and body text. It returns the new body (or the same body if no change is needed) and any error.

A rewrite hands one of these to Finalize, which applies it while it plans the ref updates: the new tag objects are written before Tier A verification runs, and the refs that point at them move with every other ref afterwards.

#ScrubMatchResult

Go go
type ScrubMatchResult struct

ScrubMatchResult is what scrub match reports -- in both modes and in both renderings. There is no separate dry-run struct: the preview's scan figures and the execution's rewrite figures are members of one result, each present exactly when it was measured.

objects_matched exists because the human preview's "in N objects" was never objects_scanned: it counts the DISTINCT objects that matched, and the two numbers used to live in disjoint branches under names close enough to read as the same thing. Both are here now, named for what they count.

#ScrubRunResult

Go go
type ScrubRunResult struct

ScrubRunResult is what scrub run reports -- in both modes and in both renderings. There is no separate dry-run struct: the preview's per-operation match counts and the execution's rewrite figures are members of one result, each present exactly when it was measured.

#ScrubRunDiffEntry

Go go
type ScrubRunDiffEntry struct

ScrubRunDiffEntry is a single blob diff in --diff preview output.

#MessageDiffEntry

Go go
type MessageDiffEntry struct

MessageDiffEntry is a commit message diff in --diff preview output.

#ScrubRunOpMatches

Go go
type ScrubRunOpMatches struct

ScrubRunOpMatches holds one operation's match counts in a preview.

#TagRewrite

Go go
type TagRewrite struct

TagRewrite records how a tag ref was updated during history rewriting.

#CommitTransform

Go go
type CommitTransform struct

CommitTransform describes how a commit should be rewritten. Zero/empty fields mean "keep the original value."

#TransformFunc

Go go
type TransformFunc func(ctx context.Context, sha string, info git.CommitInfo, remappedParents []string, shaMap map[string]string) (CommitTransform, error)

TransformFunc is called for each commit during a rewrite walk. It receives the original commit SHA, its parsed info, the already-remapped parent SHAs, and the growing old-to-new SHA map (which includes identity entries for already-walked unchanged commits; transforms must treat it as read-only — the walker owns it). It returns a CommitTransform describing what (if anything) to change.

#BuildRecipeBlobContent

Go go
func BuildRecipeBlobContent(ctx context.Context, recipe *ParsedRecipe, blobSHAs []string, blobAllowedOps map[string]map[int]bool) (map[string][]byte, error)

BuildRecipeBlobContent applies a parsed recipe to a set of blobs, producing a mapping from old blob SHA to modified content bytes. It reads each blob, applies recipe operations in memory, and returns only blobs whose content changed. No objects are written to the object store -- this is purely in-memory content computation for dry-run and diff use cases.

blobAllowedOps optionally restricts which operations apply to each blob. When nil, all operations apply to all blobs. When set, only operations whose index is in blobAllowedOps[blobSHA] are applied to that blob. This is used to enforce per-operation scope filters from recipe TOML files.

#PerPathIntent

Go go
func PerPathIntent() *RewriteIntent

PerPathIntent starts an empty per-path declaration for a walk to fill in.

#IdentityIntent

Go go
func IdentityIntent() *RewriteIntent

IdentityIntent declares a rewrite that changes identity headers only. Its Changes map carries one thing: the commits whose MESSAGE the rewrite also changed, because an identity-bearing trailer (Signed-off-by, Co-authored-by) names the same person the headers do.

#effectsRefUpdate.Update

Go go
func (u effectsRefUpdate) Update(_ context.Context, ref, newSHA, expected string) error

#remoteReadError.Error

Go go
func (e *remoteReadError) Error() string

#remoteReadError.Unwrap

Go go
func (e *remoteReadError) Unwrap() error { return e.Err }

#IntendedChange.Empty

Go go
func (c IntendedChange) Empty() bool

Empty reports whether this declaration says the commit's content is untouched. Such a commit may still be REWRITTEN -- a new commit object is created whenever a parent moved -- but its tree and message must come through identical.

#IntendedChange.PathList

Go go
func (c IntendedChange) PathList() string

PathList renders the declared paths in a stable order for an error message.

#RewriteIntent.Declare

Go go
func (ri *RewriteIntent) Declare(oldSHA string, paths []string, messageChanged bool)

Declare records what the operation decided for one commit. Repeated calls for the same commit accumulate, so a transform that runs several steps (a blob map, then a hash remap) can declare each step as it happens.

#RewriteIntent.ChangedCommits

Go go
func (ri *RewriteIntent) ChangedCommits() []string

ChangedCommits returns the OLD SHAs the operation declared a change in.

#RewriteIntent.For

Go go
func (ri *RewriteIntent) For(oldSHA string) IntendedChange

For returns the declaration for one old commit; the zero declaration means "this commit's content is untouched".

#rewriteRefusal.Error

Go go
func (e *rewriteRefusal) Error() string { return e.msg }

#RewriteResult.TierBExit

Go go
func (r *RewriteResult) TierBExit(prior int) int

TierBExit returns the exit code the command should return. A Tier B finding means the rewrite stands but something after it did not complete, which is RewriteIncomplete; otherwise the caller's own code is preserved.

#RewriteResult.Finalize

Go go
func (r *RewriteResult) Finalize(ctx context.Context, flags globalFlags, cmd string, hooks RewriteHooks) error

Finalize runs the shared post-rewrite pipeline. The whole ordering exists to put every refusable check BEFORE the first irreversible act, which is the moment a ref moves:

1. Plan every ref update, writing the new tag objects the plan needs. Objects only -- nothing is reachable, nothing has moved. 2. TIER A, all hard refusals, original history untouched: a. the preservation check -- every rewritten commit against what the operation declared it would change, plus the rewrote-count tripwire; b. the command's own Tier A hook (content verification, pattern absence over the new commit set), which reads the plan; c. the cleanliness re-check under the rewrite lock -- foreign working tree or index state that appeared WHILE the rewrite ran. 3. Capture pre-rewrite remote-tracking state and persist the rewrite-map "start" record. From here on a crash is recoverable from the journal rather than invisible -- and an abort above never wrote one, so an aborted rewrite can never read as a crashed one. 4. Apply the ref update plan. THIS is the irreversible step. 5. Persist the rewrite-map "refs" record (all tag rewrites). 6. Re-check the working tree once more and either sync it to the new HEAD or SKIP the sync, printing what to do -- foreign staged state is never overwritten. 7. untrackProtectedPaths -- remove tracked-but-gitignored files from index 8. cleanupAfterRewrite -- expire tainted reflog entries, repack, prune 9. TIER B: stale-ref pointers plus the command's own hook. Findings are recorded and reported; the rewrite stands and the exit code turns nonzero via TierBExit. 10. Resolve the new HEAD and ref, persist the "complete" record, append the oplog entry, print the push hint.

Steps 1-2 are prepare; steps 3-10 are publish. The two halves are separate methods because a rewrite that spans TWO repositories -- a submodule scrub, which rewrites the submodule and then the parent gitlinks that point at it -- has to prepare BOTH before publishing EITHER. Finalize is the single-repository spelling of prepare-then-publish, and it is what every other rewrite calls.

#gitArgs.Has

Go go
func (a gitArgs) Has(names ...string) bool

Has reports whether any of the named options is present. Names are matched exactly as spelled, so a caller asks for every spelling it cares about.

#gitArgs.Find

Go go
func (a gitArgs) Find(names ...string) (gitOption, bool)

Find returns the first occurrence of any of the named options.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search