Skip to content
internal/gitexec
On this page

The single git-execution boundary: argv construction, the subcommand classification table, and the closed list of sites exempt from the repository-root pin.

#internal/gitexec

#internal/gitexec

Package gitexec is safegit's single git-execution boundary.

Three things live here and nowhere else:

1. Process construction. Every git subprocess safegit starts is built by Command: the argv prefix, the environment assembly and the context-carried execution overrides are applied in exactly one place. 2. The git argv vocabulary. The classification table in classify.go is the single authority over which git subcommands safegit may invoke and what each of them can do to a repository. 3. The declared directory-pin exemption table (exemptions.go): the closed list of sites that are NOT subject to the repo-root working-directory pin, each with the reason it cannot be.

The package is a leaf -- it imports only the standard library -- so both internal/git (safegit's git plumbing interface) and internal/submodule (which cannot import internal/git without a cycle) route through it.

#NoDoor

Go go
const NoDoor DoorID = ""

NoDoor is the absence of a door: the call site does not let git author a commit, and an authoring argv from it is refused. It is the value every site but one passes.

#DoorRebasePassthrough

Go go
const DoorRebasePassthrough DoorID = "main.runRebase"

DoorRebasePassthrough covers main.runRebase.

#MutatesObjects

Go go
const MutatesObjects Effect = 1 << iota

MutatesObjects: the invocation can add objects to, or remove objects from, the object store.

#MutatesRefs

Go go
const MutatesRefs

MutatesRefs: the invocation can create, move or delete a ref (local or, for push, on a remote).

#MutatesIndex

Go go
const MutatesIndex

MutatesIndex: the invocation can write a git index file.

#MutatesWorktree

Go go
const MutatesWorktree

MutatesWorktree: the invocation can create, change or delete a file in the working tree.

#MutatesConfig

Go go
const MutatesConfig

MutatesConfig: the invocation can write git configuration.

#Network

Go go
const Network

Network: the invocation can contact a remote.

#ObserveOnly

Go go
const ObserveOnly Effect = 0

ObserveOnly is the empty effect set.

#KindExplicitDir

Go go
const KindExplicitDir ExemptionKind = "explicit-dir"

KindExplicitDir: the site receives a git directory and work tree as its own arguments and targets that repository, so there is no operator working directory left for a pin to correct.

#KindOperatorCwd

Go go
const KindOperatorCwd ExemptionKind = "operator-cwd"

KindOperatorCwd: the argv is the operator's own, forwarded to git verbatim. git must read it in the directory the operator typed it in; re-rooting it would silently change what the operator's pathspecs mean.

#KindEffectsHandle

Go go
const KindEffectsHandle ExemptionKind = "effects-handle"

KindEffectsHandle: safegit does not construct this subprocess -- the strictcli effects handle does, so that --dry-run records the invocation instead of performing it. The pin cannot be applied to a process safegit does not start, so each such site is directory-independent (an argv naming refs and remotes, never paths) or DECLARES the working directory to the effects handle itself, which is the pin's own value stated at the site.

#ExemptRunWithGitDir

Go go
const ExemptRunWithGitDir ExemptionID = "internal/git.RunWithGitDir"

ExemptRunWithGitDir covers git.RunWithGitDir.

#ExemptCatFileBatchAllWithDir

Go go
const ExemptCatFileBatchAllWithDir ExemptionID = "internal/git.CatFileBatchAllWithDir"

ExemptCatFileBatchAllWithDir covers git.CatFileBatchAllWithDir.

#ExemptCatFileBatchSHAsWithDir

Go go
const ExemptCatFileBatchSHAsWithDir ExemptionID = "internal/git.CatFileBatchSHAsWithDir"

ExemptCatFileBatchSHAsWithDir covers git.CatFileBatchSHAsWithDir.

#ExemptSubmoduleRunGit

Go go
const ExemptSubmoduleRunGit ExemptionID = "internal/submodule.runGit"

ExemptSubmoduleRunGit covers internal/submodule's own git runner.

#ExemptAutoBumpParentPointer

Go go
const ExemptAutoBumpParentPointer ExemptionID = "main.autoBumpParent"

ExemptAutoBumpParentPointer covers main.autoBumpParent's pointer read.

#ExemptGuardedPassthrough

Go go
const ExemptGuardedPassthrough ExemptionID = "internal/git.RunPassthrough"

ExemptGuardedPassthrough covers git.RunPassthrough.

#ExemptGitMutation

Go go
const ExemptGitMutation ExemptionID = "main.runGitMutation"

ExemptGitMutation covers main.runGitMutation.

#ExemptGitPush

Go go
const ExemptGitPush ExemptionID = "main.execGitPush"

ExemptGitPush covers main.execGitPush.

#ExemptCommitRefUpdate

Go go
const ExemptCommitRefUpdate ExemptionID = "main.effectsRefUpdate"

ExemptCommitRefUpdate covers main.effectsRefUpdate, the commit pipeline's ref update.

#ExemptHistoryRewriteRecord

Go go
const ExemptHistoryRewriteRecord ExemptionID = "main.recordHistoryRewrite"

ExemptHistoryRewriteRecord covers main.recordHistoryRewrite.

#ExemptUndoRefUpdate

Go go
const ExemptUndoRefUpdate ExemptionID = "main.recordUndoRefUpdate"

ExemptUndoRefUpdate covers main.recordUndoRefUpdate, undo's own ref move. It is a row of its own rather than the commit pipeline's: the two argv shapes differ (undo also deletes a ref), and reusing the commit row would make its identifier name a site it does not cover.

#ExemptBackupRestoreGit

Go go
const ExemptBackupRestoreGit ExemptionID = "main.runBackupGit"

ExemptBackupRestoreGit covers main.runBackupGit, the invocations backup restore mints.

#ExemptDoctorRepair

Go go
const ExemptDoctorRepair ExemptionID = "main.runRepairGit"

ExemptDoctorRepair covers main.runRepairGit, the git invocations doctor --action fix makes to repair git's own leftovers.

#Binary

Go go
const Binary = "git"

Binary is the git executable safegit invokes.

#DoorID

Go go
type DoorID string

DoorID identifies one declared door through the single-authorship boundary. The value is the code site it covers, so a reader of the table can go straight there.

#AuthoringDoor

Go go
type AuthoringDoor struct

AuthoringDoor is one row of the declared table.

#Effect

Go go
type Effect uint16

Effect is a set of things one git invocation can do to a repository.

The zero value, ObserveOnly, means the invocation changes nothing: no object is added or removed, no ref moves, no index or working-tree file is written and no remote is contacted.

#ConditionalEffect

Go go
type ConditionalEffect struct

ConditionalEffect adds effects to a verb only when one of Tokens appears in the argv. Tokens are matched literally against whole argv elements, so both option spellings ("-w", "--hard") and subcommand words ("expire", "delete") work.

#Verb

Go go
type Verb struct

Verb is one git subcommand safegit is allowed to invoke, with what that invocation can do.

Base holds the effects every invocation of the verb has; Conditional adds the effects that depend on the argv. Where a verb's effects cannot be decided from a single token, Base is deliberately the WIDER of the possibilities: a consumer that over-quarantines or under-permits is safe, one that under-quarantines is not.

#ExemptionKind

Go go
type ExemptionKind string

ExemptionKind says WHY a site is not subject to the repository-root pin.

#ExemptionID

Go go
type ExemptionID string

ExemptionID identifies one declared exemption. The value is the code site it covers, so a reader of the table can go straight there.

#DirPinExemption

Go go
type DirPinExemption struct

DirPinExemption is one row of the table.

#Spec

Go go
type Spec struct

Spec describes one git invocation.

#Error

Go go
type Error struct{ Msg string }

Error is the boundary's own error type, so a caller can tell a refusal by the boundary from a failure reported by git.

#AuthoringDoors

Go go
func AuthoringDoors() []AuthoringDoor

AuthoringDoors returns the declared table, sorted by ID. It returns a copy: the table is the boundary's, not a caller's, to change.

#Authoring

Go go
func Authoring(args []string) bool

Authoring reports whether an argv would let GIT author a commit: a verb the table marks Authors, with none of that verb's suppressing tokens present.

An argv the table does not declare, and one that names no subcommand at all, report TRUE -- the same default-deny WritesObjects and WritesWorktree take. An unknown invocation is never assumed harmless. Validate runs its vocabulary check first, so neither case ever produces the authoring refusal in practice; the default is for any other reader of this view.

#Verbs

Go go
func Verbs() []Verb

Verbs returns the declared vocabulary, sorted by name. It returns a copy.

#Lookup

Go go
func Lookup(name string) (Verb, bool)

Lookup returns the declared verb by subcommand name.

#Subcommand

Go go
func Subcommand(args []string) (string, bool)

Subcommand returns the git subcommand an argv names.

The argv may or may not carry the binary name and the global prefix; both are skipped. A global option that IS the whole invocation (git --version) is returned as the subcommand, because that is how the table declares it.

#Validate

Go go
func Validate(door DoorID, args []string) error

Validate errors when an argv names no subcommand at all, when it names one the classification table does not declare, and when its shape would let git author a commit from a call site that declares no door for it (see authoring.go).

door is the call site's declared permission to let git author. Every site but one passes NoDoor.

#EffectsOf

Go go
func EffectsOf(args []string) (Effect, error)

EffectsOf returns the effects a specific argv can have. It errors on an argv Validate would refuse.

#WritesObjects

Go go
func WritesObjects(args []string) bool

WritesObjects reports whether an argv can change the object store. This is the view Phase 3.1's object quarantine reads. An argv the table does not declare reports true: an unknown invocation is never assumed harmless.

#WritesWorktree

Go go
func WritesWorktree(args []string) bool

WritesWorktree reports whether an argv can create, change or delete a file in the working tree. This is the view the guarded passthroughs' uncommitted-work check reads: a command that cannot touch the working tree has no reason to be refused over uncommitted work, and one that can must be refused whichever way it is spelled.

Deriving it is the point. Two hand-written approximations of git's vocabulary lived in the handlers and both were wrong -- reset guarded only --hard, and bisect kept a subcommand list missing skip, run and replay. The table is the single authority over what a git invocation does, so the guard reads it rather than restating it.

An argv the table does not declare reports TRUE, the same default-deny WritesObjects takes: an unknown invocation is never assumed harmless.

#IsObserveOnly

Go go
func IsObserveOnly(args []string) bool

IsObserveOnly reports whether an argv changes nothing at all. An argv the table does not declare reports false.

#ObservePrefixes

Go go
func ObservePrefixes() [][]string

ObservePrefixes renders the table's read view as the argv PREFIXES the framework's proc-observe allowlist takes: an effects-handle invocation whose argv starts with one of them is an observe, which executes even in a dry run and is never written to the would-do log.

The list is GENERATED from the classification table rather than written out beside it, so a verb cannot become observe-authorized without the table saying it changes nothing. Two properties make the generation safe:

- A verb with any CONDITIONAL effect is excluded, however read-only its base is. The allowlist matches a PREFIX, so reflog (observe-only until expire follows it) would admit reflog expire --all -- a ref deletion executing in the middle of a dry run. Only verbs that are observe-only whatever follows them are admitted. - Each prefix carries the binary and the global prefix, because that is what an effects-handle argv literally starts with (see ArgvAny). A two-element prefix of "git" plus the global option would match every git invocation safegit makes, mutations included.

#DirPinExemptions

Go go
func DirPinExemptions() []DirPinExemption

DirPinExemptions returns the declared table, sorted by ID. It returns a copy.

#MustBeExempt

Go go
func MustBeExempt(id ExemptionID, kind ExemptionKind)

MustBeExempt asserts that id is declared with the given kind. It panics otherwise: an undeclared exemption is a programming error in safegit, not a runtime condition, and the identifiers are compile-time constants.

#GlobalPrefix

Go go
func GlobalPrefix() []string

GlobalPrefix returns the argv prefix safegit puts on every git invocation. It returns a copy: the prefix is the boundary's, not a caller's, to change.

#WithDir

Go go
func WithDir(ctx context.Context, gitDir, workTree string) context.Context

WithDir returns a context that targets a specific repository. Every git subprocess built from it sets GIT_DIR, GIT_WORK_TREE and its working directory accordingly, regardless of the process's own directory.

#DirOverride

Go go
func DirOverride(ctx context.Context) (gitDir, workTree string, ok bool)

DirOverride reports the context-carried repository override, if any.

#WithRoot

Go go
func WithRoot(ctx context.Context, root string) context.Context

WithRoot returns a context carrying the repository-root pin: every git subprocess safegit itself constructs from it runs with its working directory set to root.

The pin exists because a large part of git's plumbing vocabulary is scoped to the process working directory -- ls-files defaults to the pathspec ".", ls-tree prefixes the cwd path onto the tree it reads, apply resolves the paths inside a patch against the cwd -- so an operator invoking safegit from a subdirectory would otherwise silently narrow what safegit sees and what it rewrites. safegit's own argv is built from repo-relative or absolute paths, so pinning it to the root is what makes it mean the same thing from everywhere.

An empty root returns ctx unchanged: a repository with no work tree has no root to pin to.

#Root

Go go
func Root(ctx context.Context) (string, bool)

Root reports the context-carried repository-root pin, if any.

#WithoutRootPin

Go go
func WithoutRootPin(ctx context.Context, id ExemptionID) context.Context

WithoutRootPin returns a context whose repository-root pin does not apply, recording which declared exemption suspends it. It panics when id is not declared in the exemption table with kind KindOperatorCwd: the table is the only way a site can escape the pin.

#WithPreview

Go go
func WithPreview(ctx context.Context) context.Context

WithPreview marks a context as a preview: the --dry-run dispatch, which promises to change nothing on disk. It is what makes the quarantine ENFORCEABLE rather than merely available -- see Command, which refuses an object-writing invocation on a previewing context that carries no quarantine.

The mark is separate from the quarantine itself precisely so the refusal has something to fire on: a previewing command that forgot to install a quarantine is exactly the case worth catching.

#InPreview

Go go
func InPreview(ctx context.Context) bool

InPreview reports whether this context belongs to a preview.

#WithObjectQuarantine

Go go
func WithObjectQuarantine(ctx context.Context, dir string, alternates ...string) context.Context

WithObjectQuarantine returns a previewing context whose git subprocesses write every object they create into dir instead of into the repository, while still reading the object stores named by alternates.

A preview that promises to change nothing cannot make that promise while git add, git write-tree and git commit-tree deposit blobs, trees and commits in the repository's own store, where they stay as unreferenced loose objects. Pointing GIT_OBJECT_DIRECTORY at a throwaway directory keeps the preview's arithmetic exact -- the tree SHA it computes is the tree SHA the real run would compute -- and leaves nothing behind when the directory goes.

It marks the context as a preview too: a quarantine exists for no other reason.

One constraint the caller has to know: the only object store Command derives for itself is the one behind Spec.GitDir. A spec that targets another repository through Spec.Dir or Spec.WorkTree ALONE gets the quarantine but no alternate for its own objects, so under this context it could not read them -- where a work tree keeps its object store cannot be derived from a directory without guessing (a linked worktree, a bare repository and a redirected store all break the .git/objects join). No site reaches that combination today, and TestQuarantineLeavesADirOnlySpecUnableToReadItsOwnObjects pins it; a site that needs to must name GitDir alongside Dir.

#ObjectQuarantine

Go go
func ObjectQuarantine(ctx context.Context) (dir string, alternates []string, ok bool)

ObjectQuarantine reports the context-carried object quarantine, if any.

#Command

Go go
func Command(ctx context.Context, s Spec) (*exec.Cmd, error)

Command builds the git subprocess for one spec.

It errors when the argv names a subcommand the classification table does not declare, and when the spec's directory targeting is not backed by a declared exemption. It never starts the process; the caller owns that. A spec carries no single-authorship door, and cannot: the argv Command builds is one safegit constructs and starts ITSELF, and none of those may let git author a commit. The one declared door is on the effects-handle path (ArgvAny), because the command that has it -- the rebase passthrough -- runs there. A site that ever needs a door here has to add the field deliberately, which is the point.

#ArgvAny

Go go
func ArgvAny(exempt ExemptionID, door DoorID, args ...string) ([]interface{}, error)

ArgvAny returns the full git argv -- binary, global prefix, then args -- as []interface{}, the shape the strictcli effects handle takes.

The invocation is not built by Command because safegit does not start it: the effects handle does, in the process working directory. exempt names the declared reason that is acceptable.

It takes no context, so the context-carried overrides Command applies -- the object quarantine among them -- cannot reach these invocations. That is not a hole, because no argv built here can write objects during a preview:

- An argv matching an allowlisted observe prefix EXECUTES even in dry mode, but ObservePrefixes admits only verbs the classification table declares observe-only unconditionally, which by definition write nothing. - Every other argv is RECORDED in dry mode and never started, whatever it names -- which is how repack and prune appear in a rewrite preview's would-do log without ever running.

Outside a preview there is no quarantine to reach in the first place: an executing run writes its objects into the repository on purpose.

door is the call site's declared permission to let git author a commit. Every site but the rebase passthrough passes NoDoor; see authoring.go.

#Effect.Has

Go go
func (e Effect) Has(want Effect) bool { return e&want == want }

Has reports whether every effect in want is present in e.

#Effect.String

Go go
func (e Effect) String() string

String renders an effect set for diagnostics.

#Error.Error

Go go
func (e *Error) Error() string { return e.Msg }
Search