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
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
const IndexBaseParentTree IndexBase = iotaIndexBaseParentTree 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
const IndexBaseSharedIndexIndexBaseSharedIndex 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
const IndexEditBlob IndexEditKind = iotaIndexEditBlob places Mode and SHA at stage 0, replacing every slot the path holds.
#IndexEditWorktree
const IndexEditWorktreeIndexEditWorktree stages the working-tree file at Path, whatever it now holds, replacing every slot the path holds.
#IndexEditRemove
const IndexEditRemoveIndexEditRemove removes every slot the path holds, so the commit does not contain it. The working-tree file is not touched.
#ErrTreeUnchanged
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
type AmendRequest structAmendRequest holds inputs for an amend operation.
#AmendResult
type AmendResult structAmendResult is the JSON-serializable output of a successful amend.
#RewordRequest
type RewordRequest structRewordRequest holds inputs for a reword operation.
#RewordResult
type RewordResult structRewordResult is the JSON-serializable output of a successful reword.
#CommitError
type CommitError structCommitError 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
type PartialError structPartialError 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
type Pipeline structPipeline orchestrates the full commit flow.
#FileSpec
type FileSpec structFileSpec describes a file with optional hunk selection for staging.
#CommitRequest
type CommitRequest structCommitRequest holds all inputs for a single commit operation.
#IndexBase
type IndexBase intIndexBase 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
type CommitResult structCommitResult is the JSON-serializable output of a successful commit.
#IndexEditKind
type IndexEditKind intIndexEditKind names what one edit does to the temporary index.
#IndexEdit
type IndexEdit structIndexEdit 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
type RefusedMove structRefusedMove 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
type RefUpdate interfaceRefUpdate 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
func NativeHooks() []stringNativeHooks 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
func ApplyIndexEditsTo(ctx context.Context, indexPath string, edits []IndexEdit) errorApplyIndexEditsTo 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
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
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
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
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
func (e *CommitError) Error() string { return e.Message }Error returns the error message.
#CommitError.Unwrap
func (e *CommitError) Unwrap() error { return e.Err }Unwrap exposes the underlying cause to errors.Is/errors.As.
#PartialError.Error
func (e *PartialError) Error() stringError states the outcome in the order it has to be read: the commit first, the failure second.
#PartialError.Unwrap
func (e *PartialError) Unwrap() error { return e.Err }Unwrap exposes the cause to errors.Is/errors.As.
#Pipeline.Execute
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.