commit

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BranchFromRefspec

func BranchFromRefspec(refspec string) string

BranchFromRefspec extracts the branch name from a refspec like "HEAD:refs/heads/main".

func Replay added in v0.6.0

func Replay(session *gitstate.SyncSession) error

Replay rebases local commits onto upstream using controlled tree replay. It is NOT a raw git rebase — it replays only SF-generated commits via explicit tree diffing with full integrity verification at each step.

Algorithm:

  1. Pre-conditions: HEAD attached, worktree clean, upstream configured → fail fast, no mutation
  2. Gate: no merge commits, linear chain (structural only — no authorship constraints)
  3. Re-validate upstream hash non-zero; record fetchedUpstreamHash
  4. Compute merge-base (exactly 1); collect commits oldest-first
  5. Hard reset to upstream
  6. For each commit: apply diff, stage, verify staging states, commit
  7. Race guard: upstream unchanged since fetch

Push is NOT performed by Replay — the engine (Engine.doReplayThenPush) owns push so the transition DIVERGED → REPLAY → CLEAN_AHEAD → PUSH remains in the engine's state machine and the push is logged as a formal transition.

Hooks are NOT run during replay — replay commits are machine-generated re-applications; running hooks again would double-execute side effects.

func RunCommitMsgHook added in v0.6.0

func RunCommitMsgHook(repoRoot, message string, cb func(stream, line string)) (string, error)

RunCommitMsgHook writes the message to a temp file, executes .git/hooks/commit-msg, and re-reads the (possibly modified) message from the file. Returns the (possibly modified) message and any error.

func RunHook added in v0.6.0

func RunHook(repoRoot, hookName string, args []string, input []byte, cb func(stream, line string)) error

RunHook executes a git hook if it exists and is executable. Returns ErrHookRejected if the hook exits non-zero. cb receives incremental stdout/stderr lines — never silently swallowed.

Hook sequence (caller's responsibility to run in order):

  1. pre-commit — no args, stdin /dev/null. Non-zero → abort.
  2. commit-msg <tmpfile> — write message to tmpfile, pass path as arg. Hook may modify the file; re-read after. Non-zero → abort.
  3. wt.Commit() — called by the CommitEngine, not here.
  4. post-commit — no args. Non-zero → warning only, no abort.

Hooks are treated as untrusted side-effect producers: - stdout/stderr always surfaced via cb - worktree snapshot before/after pre-commit; any new dirty paths emitted as hook_side_effect - filesystem mutations outside commit-msg tmpfile are detected and reported

func RunPostCommitHook added in v0.6.0

func RunPostCommitHook(repoRoot string, cb func(stream, line string))

RunPostCommitHook executes .git/hooks/post-commit. Non-zero exit is logged as a warning via cb but does NOT abort.

func RunPreCommitHook added in v0.6.0

func RunPreCommitHook(repoRoot string, wt *git.Worktree, cb func(stream, line string)) error

RunPreCommitHook executes .git/hooks/pre-commit and snapshots worktree state before/after, emitting hook_side_effect diagnostics for new dirty paths.

Types

type Backend

type Backend interface {
	Execute(ctx context.Context, plan *Plan, conventional bool) (*Result, error)
}

Backend executes a commit plan.

type CommitBlock added in v0.6.0

type CommitBlock struct {
	Ring    CommitRing
	ID      string // stable identifier for programmatic use and output
	Message string // human-readable explanation
}

CommitBlock is a classified failure at any stage of commit planning or execution. Hard rings (Mechanical, Determinism) propagate as errors. Governance rings accumulate and are either reported or bypassed.

func (CommitBlock) Overrideable added in v0.6.0

func (b CommitBlock) Overrideable() bool

Overrideable returns true when the block can be bypassed with --maintainer-override.

type CommitBlocks added in v0.6.0

type CommitBlocks []CommitBlock

CommitBlocks is an ordered collection of CommitBlock values.

func (CommitBlocks) GovernanceOnly added in v0.6.0

func (bs CommitBlocks) GovernanceOnly() CommitBlocks

GovernanceOnly returns a slice containing only governance-ring blocks.

func (CommitBlocks) HasDeterminism added in v0.6.0

func (bs CommitBlocks) HasDeterminism() bool

HasDeterminism returns true when any RingDeterminism block is present. UX message: "StageFreight cannot safely describe what will be committed".

func (CommitBlocks) HasGovernance added in v0.6.0

func (bs CommitBlocks) HasGovernance() bool

HasGovernance returns true when any overrideable governance block is present.

func (CommitBlocks) HasHard added in v0.6.0

func (bs CommitBlocks) HasHard() bool

HasHard returns true when any non-overrideable block is present. Use HasMechanical and HasDeterminism for UX-distinct messaging.

func (CommitBlocks) HasMechanical added in v0.6.0

func (bs CommitBlocks) HasMechanical() bool

HasMechanical returns true when any RingMechanical block is present. UX message: "fix your repository state".

type CommitRing added in v0.6.0

type CommitRing string

CommitRing classifies a commit failure by its relationship to the commit path's safety and determinism. The ring determines whether a failure is absolute or can be bypassed with explicit maintainer intent.

Three rings, ordered by overrideability:

RingMechanical   → git integrity is broken. No escape.
RingDeterminism  → StageFreight cannot reliably describe what will happen. No escape.
RingGovernance   → StageFreight policy or model is degraded, but the commit
                   path is still deterministic. Bypassed by --maintainer-override.

The rule: do not let Ring 3 failures disable a still-deterministic commit path.

const (
	// RingMechanical covers failures where the git repository, index, or
	// filesystem state makes committing impossible regardless of intent:
	//   - detached HEAD
	//   - unresolved rebase/merge conflict
	//   - index write failure
	//   - hook rejected the commit
	//   - push explicitly requested but mechanically blocked
	// Cannot be overridden.
	RingMechanical CommitRing = "mechanical"

	// RingDeterminism covers failures where StageFreight cannot reliably
	// determine what will be committed, construct the message, or truthfully
	// describe the result:
	//   - missing commit summary
	//   - unresolvable path set
	//   - sync plan logically impossible (not a git error, a planning error)
	// Cannot be overridden.
	RingDeterminism CommitRing = "determinism"

	// RingGovernance covers failures where StageFreight policy, schema, or
	// model is degraded but the commit path itself remains deterministic:
	//   - partial config-model migration incomplete
	//   - generated outputs stale (docs, badges, manifests)
	//   - convention strictness failures
	//   - unrelated StageFreight subsystem broken
	// Can be bypassed with --maintainer-override.
	// Bypasses are recorded in Result.OverriddenBlocks and printed to output.
	RingGovernance CommitRing = "governance"
)

type DryRunBackend

type DryRunBackend struct {
	RootDir string
}

DryRunBackend prints the commit plan without side effects.

func (*DryRunBackend) Execute

func (d *DryRunBackend) Execute(_ context.Context, plan *Plan, conventional bool) (*Result, error)

Execute simulates the commit and returns what would happen.

type Engine added in v0.6.0

type Engine struct {
	// contains filtered or unexported fields
}

Engine orchestrates repository convergence as an explicit state machine.

Every mutation is a validated transition between named states. No operation is performed outside of a valid (from, action, to) triple.

Transition table:

CLEAN_SYNCED  → (no-op)                              → CLEAN_SYNCED
CLEAN_BEHIND  → FAST_FORWARD                         → CLEAN_SYNCED
CLEAN_AHEAD   → PUSH                                 → CLEAN_SYNCED
DIVERGED      → REPLAY → CLEAN_AHEAD → PUSH          → CLEAN_SYNCED

Blocked states (hard stops — no transitions allowed):

DETACHED_HEAD, DIRTY_WORKTREE, NO_UPSTREAM

When RebaseOnDiverge is false, DIVERGED returns ErrInvalidTransition instead of attempting replay — the caller must resolve the divergence manually.

func NewEngine added in v0.6.0

func NewEngine(session *gitstate.SyncSession, opts EngineOptions) *Engine

NewEngine creates an Engine bound to the given SyncSession.

func (*Engine) Sync added in v0.6.0

func (e *Engine) Sync() (*SyncResult, error)

Sync drives the repository to CLEAN_SYNCED through the minimum valid transition path determined by the current state class.

Algorithm:

  1. Classify initial state — fail immediately on any X-class blocked state
  2. Fetch + reclassify (fetch is always required for an accurate view)
  3. Dispatch the single valid transition for the resulting state class

Returns the SyncResult describing exactly what happened.

type EngineOptions added in v0.6.0

type EngineOptions struct {
	// RebaseOnDiverge: when true, DIVERGED state triggers Replay + Push.
	// When false, DIVERGED returns ErrInvalidTransition — the user must
	// resolve the divergence manually before pushing.
	RebaseOnDiverge bool

	// Remote overrides the remote resolved from the session state.
	// Empty means "use whatever the branch tracking config says".
	Remote string

	// Refspec is an optional explicit push refspec (e.g. "HEAD:refs/heads/main").
	// Empty means push the current branch to its upstream.
	Refspec string

	// OnEvent receives a structured event for every state transition.
	// If nil, events are silently dropped.
	// Use this for CI log output, tracing, or UI state panels.
	OnEvent func(gitstate.TransitionEvent)
}

EngineOptions configures Engine behaviour.

type FileChange

type FileChange struct {
	Path    string
	Deleted bool
}

FileChange represents a file with its change status.

type ForgeBackend

type ForgeBackend struct {
	RootDir     string
	ForgeClient forge.Forge
	Branch      string
}

ForgeBackend creates commits purely via forge API (no local git commit).

func (*ForgeBackend) Execute

func (f *ForgeBackend) Execute(ctx context.Context, plan *Plan, conventional bool) (*Result, error)

Execute resolves changed files, reads their content, and commits via forge API.

type GitBackend

type GitBackend struct {
	RootDir string
	// OnCommitLine is called for each output line during hook execution and sync
	// transition events. stream: "stdout", "stderr", "hook_side_effect", "sync".
	// If nil, output is captured but not forwarded.
	OnCommitLine func(stream string, line string)
}

GitBackend executes commits via go-git — no git binary required. Push/sync is also handled via go-git through the Engine.

func (*GitBackend) Execute

func (g *GitBackend) Execute(_ context.Context, plan *Plan, conventional bool) (*Result, error)

Execute stages files, creates a commit, and optionally pushes.

func (*GitBackend) Push added in v0.6.0

func (g *GitBackend) Push(opts PushOptions) (*SyncResult, error)

Push synchronizes the current branch with its remote via the go-git engine.

type Plan

type Plan struct {
	Type      string
	Scope     string
	Summary   string
	Body      string
	Breaking  bool
	SkipCI    bool
	Paths     []string // for StageExplicit
	StageMode StageMode
	Push      PushOptions
	SignOff   bool
}

Plan is a fully resolved, validated commit intent.

func BuildPlan

func BuildPlan(opts PlannerOptions, cfg config.CommitConfig, registry *TypeRegistry, rootDir string) (*Plan, error)

BuildPlan merges CLI flags with config defaults, validates, and returns a Plan.

func (Plan) Message

func (p Plan) Message(conventional bool) string

Message renders the full commit message (subject + optional body). The SF-generated trailer is always appended so that the replay gate can identify and safely rebase these commits.

func (Plan) Subject

func (p Plan) Subject(conventional bool) string

Subject renders the commit subject line. When conventional is true: {type}[({scope})][!]: {summary}[ [skip ci]] When conventional is false: {summary}[ [skip ci]]

type PlannerOptions

type PlannerOptions struct {
	Type     string
	Scope    string
	Message  string // positional or --message
	Body     string
	Breaking bool
	SkipCI   *bool    // nil = use config default
	Push     *bool    // nil = use config default
	Paths    []string // from --add flags
	All      bool
	SignOff  bool
	Remote   string
	Refspec  string
}

PlannerOptions holds raw inputs from CLI flags and positional args.

type PushOptions

type PushOptions struct {
	Enabled         bool
	Remote          string // default: "origin"
	Refspec         string // default: "" (current branch)
	RebaseOnDiverge bool   // when true (default), rebase onto upstream if diverged before pushing
}

PushOptions controls post-commit push behavior.

type Result

type Result struct {
	SHA     string
	Message string
	Files   []string // actual staged files (from git diff --cached --name-only)
	Pushed  bool
	NoOp    bool
	Backend string      // stable descriptor: "git", "forge (gitlab)", "forge (github)", "dry-run"
	Sync    *SyncResult // populated when push was executed via the convergence engine

	// MaintainerOverride is true when --maintainer-override was active for this commit.
	// OverriddenBlocks records which governance checks were bypassed.
	// Both are empty for normal commits with no governance failures.
	MaintainerOverride bool
	OverriddenBlocks   CommitBlocks
}

Result holds the outcome of a commit execution. All informational status must be represented here, not printed by backends.

type StageMode

type StageMode string

StageMode determines how files are staged before commit.

const (
	StageExplicit StageMode = "explicit" // --add paths
	StageAll      StageMode = "all"      // --all (git add -A)
	StageStaged   StageMode = "staged"   // default: commit whatever is already staged
)

type SyncAction added in v0.6.0

type SyncAction string

SyncAction names a single step in the repository convergence plan.

const (
	SyncSetUpstream SyncAction = "set-upstream" // configure tracking branch on first push
	SyncFetch       SyncAction = "fetch"        // fetch remote before rebase or fast-forward
	SyncFastForward SyncAction = "fast-forward" // merge --ff-only to catch up to upstream
	SyncRebase      SyncAction = "rebase"       // rebase local commits onto upstream
	SyncPush        SyncAction = "push"         // push to remote
	SyncNoop        SyncAction = "noop"         // already up to date, nothing to do
)

type SyncResult added in v0.6.0

type SyncResult struct {
	ActionsExecuted []SyncAction
	PushedRef       string // remote name that was pushed to
	Noop            bool   // true only when SyncNoop was the sole action
}

SyncResult is the outcome of a push operation.

type TypeRegistry

type TypeRegistry struct {
	// contains filtered or unexported fields
}

TypeRegistry validates and resolves commit type keys.

func NewTypeRegistry

func NewTypeRegistry(types []config.CommitType) *TypeRegistry

NewTypeRegistry creates a registry from configured commit types.

func (*TypeRegistry) List

func (r *TypeRegistry) List() []config.CommitType

List returns all configured commit types in definition order.

func (*TypeRegistry) Resolve

func (r *TypeRegistry) Resolve(key string) (resolvedKey string, forceBang bool, err error)

Resolve returns the resolved type key and whether a bang (!) is forced. If the type has an AliasFor, it returns the alias target key.

func (*TypeRegistry) Valid

func (r *TypeRegistry) Valid(key string) bool

Valid returns true if the key is a recognized commit type.

Jump to

Keyboard shortcuts

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