Skip to content
internal/commit
Edit
On this page

Package commit implements the two-phase commit pipeline: a parallel-safe staging phase and a serialized ref-update phase with CAS retries.

#internal/commit

#internal/commit

Amend and Reword implement tip-commit rewriting with CAS safety.

#StepIndexReconcile

Go go
const StepIndexReconcile = "reconciling the shared index"

StepIndexReconcile is the pipeline's own aftercare: putting the repository's shared index in step with the commit that was just made. It is the only step the pipeline itself performs after the ref update, so it is the only member of the vocabulary this package declares; the rest of the family's steps belong to the commands that run them.

#IndexBaseParentTree

Go go
const IndexBaseParentTree IndexBase = iota

IndexBaseParentTree seeds the temporary index from the tree of the commit being built on -- the branch tip, or an empty index on an unborn ref. It is the zero value and what every ordinary commit uses: the commit contains the parent's content with the named paths applied over it.

#IndexBaseSharedIndex

Go go
const IndexBaseSharedIndex

IndexBaseSharedIndex seeds the temporary index from a copy of the repository's shared index (.git/index), so that whatever is staged there becomes the commit's content. It exists for the conclusion of an operation git has in flight, where the conflict resolution the operator staged lives in that index and nowhere else. The copy is a copy: the shared index is read and never written.

#IndexEditBlob

Go go
const IndexEditBlob IndexEditKind = iota

IndexEditBlob places Mode and SHA at stage 0, replacing every slot the path holds.

#IndexEditWorktree

Go go
const IndexEditWorktree

IndexEditWorktree stages the working-tree file at Path, whatever it now holds, replacing every slot the path holds.

#IndexEditRemove

Go go
const IndexEditRemove

IndexEditRemove removes every slot the path holds, so the commit does not contain it. The working-tree file is not touched.

#ErrTreeUnchanged

Go go
var ErrTreeUnchanged = errors.New("the commit's tree is identical to its parent's")

ErrTreeUnchanged is the cause behind the empty-commit refusal, so a caller can recognize that particular refusal without matching on its text.

The refusal's own message names --allow-empty, which is the answer for safegit commit. It is not the answer for a caller that has no such flag: a conclusion command wraps this with the ways out that exist for IT rather than pointing at a flag it does not offer.

#AmendRequest

Go go
type AmendRequest struct

AmendRequest holds inputs for an amend operation.

#AmendResult

Go go
type AmendResult struct

AmendResult is the JSON-serializable output of a successful amend.

#RewordRequest

Go go
type RewordRequest struct

RewordRequest holds inputs for a reword operation.

#RewordResult

Go go
type RewordResult struct

RewordResult is the JSON-serializable output of a successful reword.

#CommitError

Go go
type CommitError struct

CommitError carries a structured exit code alongside the error message.

It reaches the caller wrapped as often as not -- a staging failure is annotated with the path it happened on before it leaves the pipeline -- so callers must find it with errors.As, never with a bare type assertion.

#PartialError

Go go
type PartialError struct

PartialError is the pipeline's commit-stands verdict: the ref MOVED, the commit object is the branch's tip, and a step that runs after the ref update did not finish.

It is a distinct type rather than a CommitError with a code because the two say opposite things about what happened. A CommitError is a refusal -- nothing was written, and re-running the command after fixing the cause is the remedy. This says the operation succeeded and something it owed afterwards did not, so re-running would make a SECOND commit. A caller that cannot tell them apart retries by default, which is the expensive guess.

The result value is returned ALONGSIDE it, not instead of it: a caller has to be able to report which commit stands, and that is the result's job.

#Pipeline

Go go
type Pipeline struct

Pipeline orchestrates the full commit flow.

#FileSpec

Go go
type FileSpec struct

FileSpec describes a file with optional hunk selection for staging.

#CommitRequest

Go go
type CommitRequest struct

CommitRequest holds all inputs for a single commit operation.

#IndexBase

Go go
type IndexBase int

IndexBase selects what a commit's temporary index starts from.

It is an explicit input rather than something inferred, because the two answers mean different things about where the commit's content came from: the parent tree plus the paths the caller named, or a resolution the operator already staged.

#CommitResult

Go go
type CommitResult struct

CommitResult is the JSON-serializable output of a successful commit.

#IndexEditKind

Go go
type IndexEditKind int

IndexEditKind names what one edit does to the temporary index.

#IndexEdit

Go go
type IndexEdit struct

IndexEdit is one caller-decided change to the temporary index, applied after the index is seeded and before anything else is staged.

It carries no conflict vocabulary on purpose. Deciding that --resolve path=theirs means "the stage-3 blob" is the conclusion engine's job, and it is done once, against the shared index, before the pipeline runs; what arrives here is a mode, an object name and a path, which the pipeline applies without interpreting. That split is what keeps the pipeline free of any opinion about merges while still writing the objects inside a dry run's quarantine and re-applying every edit on a compare-and-swap retry.

#RefusedMove

Go go
type RefusedMove struct

RefusedMove is one move safegit's delta suggested and its fences declined to record: the paths on each side and why nothing was written.

A refusal is reported rather than dropped, because the caller who performed the move needs to know safegit did not record it.

They ride the commit payload itemized, and the aggregate stderr notice counts them by reason.

#RefUpdate

Go go
type RefUpdate interface

RefUpdate performs the compare-and-swap that makes a commit the branch's tip -- the ONE mutation the commit pipeline makes on the world.

It is an input rather than a call into internal/git because that update is the seam a preview stops at: the caller's implementation mints it through the framework's effects handle, which performs it in an executing run and RECORDS it in a dry run, so a preview's would-do log states the move it would make instead of rendering an empty body. There is exactly one mint site, inside the compare-and-swap retry loop; a second one alongside it would fire twice per commit and record a move the loop had already made.

The interface is the pipeline's own rather than the framework's handle type because the handle's result carrier cannot be constructed outside the framework (its settled-ness is unexported and every accessor panics when unsettled), which would leave this package's own tests unable to supply one. Production has a single implementation and it IS the handle.

#NativeHooks

Go go
func NativeHooks() []string

NativeHooks returns the git hooks safegit itself executes, in the order a commit reaches them. It is exported because doctor reports the hooks safegit does NOT run, and that report is only true while it derives the set from here rather than restating it.

#ApplyIndexEditsTo

Go go
func ApplyIndexEditsTo(ctx context.Context, indexPath string, edits []IndexEdit) error

ApplyIndexEditsTo applies the same edits to an index OUTSIDE the pipeline, resolving the repository root itself.

It exists for one caller: a conclusion, once its commit is real, has to put the shared index in the same state before reconciling it, because the reconciliation deliberately preserves unmerged stages and would otherwise preserve the very conflict the conclusion just resolved. An empty indexPath means the shared index.

#CanonicalRel

Go go
func CanonicalRel(repoRoot, arg string, followFinal bool) (string, error)

CanonicalRel is canonicalRel for a caller outside this package.

It exists for safegit mv, whose arguments are paths a person typed at a shell prompt exactly as a positional path is, and which must therefore mean the same thing from a subdirectory as from the root. One canonicalizer, so a path named in a mv argument and the same path named anywhere else in the commit family resolve to the same repo-relative spelling.

#BeginPreview

Go go
func BeginPreview(ctx context.Context, dryRun bool) (previewCtx context.Context, area string, cleanup func(), err error)

BeginPreview opens the throwaway area a dry run works in, and returns the context every git call of that run must be made with.

It is the SINGLE preview-area constructor for the whole tool. It lives here because the commit pipeline was the first command family to write objects in a preview, and it is exported because it is no longer the only one: the honest --dry-run of merge, cherry-pick and revert computes its answer with git merge-tree --write-tree, which writes real tree and blob objects and therefore needs exactly this quarantine. Two constructors would be two answers to "where does a preview put the objects it makes".

A preview computes real answers: it stages into an index, writes a tree and (for a commit or an amend) builds the commit object, because that is the only honest way to report which paths the operation would change. Every one of those steps writes objects. Pointing GIT_OBJECT_DIRECTORY at a directory inside the preview area is what keeps the arithmetic exact while leaving the repository's own object store untouched -- the tree SHA the preview reports is the tree SHA the real run would produce, and the objects behind it go away with the area.

Ordering, which is not incidental: the repository's object store is resolved BEFORE the quarantine is installed and the quarantine directory is created BEFORE it is named in an environment, because a GIT_OBJECT_DIRECTORY that points at a directory that does not exist makes git fail repository discovery outright ("not a git repository").

The area's lifetime is the whole operation, not one compare-and-swap attempt: the retry loop stages again from scratch each time, and an area per attempt would multiply directories for no gain. Not a dry run returns the context unchanged and an empty area, which is the signal to stage under the safegit directory as an executing run does.

#Pipeline.Amend

Go go
func (p *Pipeline) Amend(ctx context.Context, req AmendRequest) (*AmendResult, error)

Amend rewrites the tip of the current branch with new files staged. Uses tmp index seeded from HEAD, stages files, builds a new commit with parent = HEAD^ and lock-and-CAS updates the ref.

#Pipeline.Reword

Go go
func (p *Pipeline) Reword(ctx context.Context, req RewordRequest) (*RewordResult, error)

Reword rewrites only the commit message of the tip of the current branch. Tree and parent remain unchanged. Retries on CAS miss.

#CommitError.Error

Go go
func (e *CommitError) Error() string { return e.Message }

Error returns the error message.

#CommitError.Unwrap

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

Unwrap exposes the underlying cause to errors.Is/errors.As.

#PartialError.Error

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

Error states the outcome in the order it has to be read: the commit first, the failure second.

#PartialError.Unwrap

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

Unwrap exposes the cause to errors.Is/errors.As.

#Pipeline.Execute

Go go
func (p *Pipeline) Execute(ctx context.Context, req CommitRequest) (*CommitResult, error)

Execute runs the full two-phase commit pipeline. On CAS miss it retries from Phase A up to Config.Commit.CASMaxAttempts times.

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