Documentation
¶
Overview ¶
Package git wraps the git CLI for everflow's worktree + commit + push operations. See ADR-0023 for why we shell out instead of using go-git.
All methods on Git take the worktree directory as their first parameter. Auth (SSH keys, credential helpers, GIT_ASKPASS) is the host's responsibility — the daemon does not manage credentials.
Index ¶
- Variables
- type ExecGit
- func (g *ExecGit) Commit(ctx context.Context, dir, message string) error
- func (g *ExecGit) ConflictedFiles(ctx context.Context, dir string) ([]string, error)
- func (g *ExecGit) DiffShortstat(ctx context.Context, dir, baseBranch string) (string, error)
- func (g *ExecGit) EnsureBranch(ctx context.Context, dir, baseRepo, baseBranch, branchName string) error
- func (g *ExecGit) HardReset(ctx context.Context, dir, baseBranch string) error
- func (g *ExecGit) HasChanges(ctx context.Context, dir string) (bool, error)
- func (g *ExecGit) HasWorkBeyondBase(ctx context.Context, dir, baseBranch string) (bool, error)
- func (g *ExecGit) IsIsolatedWorktree(ctx context.Context, dir string) (bool, error)
- func (g *ExecGit) Push(ctx context.Context, dir, branchName string) error
- func (g *ExecGit) RemoveWorktree(ctx context.Context, baseRepo, dir string) error
- func (g *ExecGit) SyncWithBase(ctx context.Context, dir, baseBranch string) error
- type Git
- type HookRejectionError
Constants ¶
This section is empty.
Variables ¶
var ErrNoChanges = errors.New("git: no changes to commit")
ErrNoChanges is returned by Commit when the worktree is clean. Callers that want to treat clean-runner output as a real failure should check against this sentinel.
Functions ¶
This section is empty.
Types ¶
type ExecGit ¶
type ExecGit struct {
// Author identity for commits. If empty, falls back to the daemon's
// host git config.
AuthorName string
AuthorEmail string
}
ExecGit shells out to the `git` binary. The zero value is usable.
func NewExec ¶
NewExec returns an ExecGit. Both Author fields are optional; if unset, commits inherit the host's `user.name` / `user.email` from .gitconfig.
func (*ExecGit) ConflictedFiles ¶
func (*ExecGit) DiffShortstat ¶
func (*ExecGit) EnsureBranch ¶
func (*ExecGit) HasChanges ¶
func (*ExecGit) HasWorkBeyondBase ¶
func (*ExecGit) IsIsolatedWorktree ¶
func (*ExecGit) RemoveWorktree ¶
type Git ¶
type Git interface {
// EnsureBranch makes sure `dir` is a git worktree on `branchName`,
// rooted at `baseRepo`, branched off `origin/baseBranch`. Idempotent —
// safe to call when the worktree already exists (validates and uses it).
EnsureBranch(ctx context.Context, dir, baseRepo, baseBranch, branchName string) error
// HardReset fetches origin/baseBranch and forces `dir` to match it,
// discarding any local commits or working-tree changes. Used by the
// planning worktree to refresh between iterations so the planner
// always sees the current state of base.
HardReset(ctx context.Context, dir, baseBranch string) error
// HasChanges reports whether the worktree at `dir` has uncommitted
// modifications (staged or unstaged, including untracked files).
HasChanges(ctx context.Context, dir string) (bool, error)
// HasWorkBeyondBase reports whether the worktree at `dir` contains any
// work of its own relative to origin/<baseBranch>: uncommitted
// modifications OR commits beyond the merge-base with origin/<baseBranch>.
//
// This is the "did the runner do anything?" check. HasChanges alone is
// wrong for that purpose when the runner commits its own work — the
// tree is clean afterwards, and porcelain-only detection would discard
// the real work as "no changes". Using the merge-base (rather than
// comparing HEAD to origin/<baseBranch> directly) means a base branch
// that moved forward while the unit sat idle does NOT count as work.
//
// The four cases:
// - runner committed its own work (clean tree) → true
// - runner left uncommitted changes (dirty tree) → true
// - fresh worktree, runner did nothing → false
// - base moved forward, unit idle → false
//
// Purely local — reads the origin/<baseBranch> tracking ref as last
// fetched; it does not fetch. HasChanges remains the right check for
// the Commit flow (is there anything to stage?).
HasWorkBeyondBase(ctx context.Context, dir, baseBranch string) (bool, error)
// Commit stages every change in the worktree and creates a commit with
// the given message. Returns ErrNoChanges if nothing was staged — the
// caller decides whether that's worth treating as an error.
Commit(ctx context.Context, dir, message string) error
// Push pushes branchName to origin, setting upstream. Auth is the host's
// responsibility; this method does not embed credentials in URLs.
Push(ctx context.Context, dir, branchName string) error
// RemoveWorktree tears down the worktree at `dir` and prunes the parent
// repo's worktree registration. Idempotent — succeeds even if `dir`
// is already gone.
RemoveWorktree(ctx context.Context, baseRepo, dir string) error
// SyncWithBase fetches origin/baseBranch and merges it into dir's current
// branch, so the branch's own commits are preserved but base has moved
// forward to its current tip. Unlike HardReset (which discards local
// commits — used for the planning worktree), this is for worktrees with
// in-flight commits of their own: it refreshes the view of base without
// throwing that work away. Called before address-comment / fix-CI runner
// invocations so conflict resolution never judges against a stale base
// (see ADR-0045).
//
// SyncWithBase requires a clean worktree: if `dir` has uncommitted
// changes (e.g. from an interrupted invocation) it returns an error
// without fetching or merging, so those changes are never silently
// merged over.
//
// If the merge produces conflicts, that's a legitimate outcome, not an
// error: SyncWithBase returns nil and leaves the worktree with unmerged
// paths for the runner to resolve as part of its turn. Only failures
// that aren't ordinary merge conflicts (fetch failure, unknown branch,
// etc.) are returned as errors.
SyncWithBase(ctx context.Context, dir, baseBranch string) error
// ConflictedFiles lists the paths git currently reports as unmerged in
// `dir` (diff --diff-filter=U), i.e. the files left conflicted by a prior
// SyncWithBase merge. Called after SyncWithBase so the runner's conflict-
// resolution turn knows exactly which files to look at instead of
// re-deriving it from a full worktree scan.
ConflictedFiles(ctx context.Context, dir string) ([]string, error)
// DiffShortstat returns the `--shortstat` summary of commits reachable from
// HEAD but not from origin/<baseBranch>, e.g.
// "3 files changed, 12 insertions(+), 4 deletions(-)".
// Returns an empty string if HEAD == origin/<baseBranch> (no commits yet).
// Used to append the actual diff extent to MR comments so reviewers can
// compare the runner's summary against what was really pushed (item 4 of
// ADR-TBD hallucination guard).
DiffShortstat(ctx context.Context, dir, baseBranch string) (string, error)
// IsIsolatedWorktree reports whether `dir` is a real linked git worktree
// — i.e. it has its own git-dir distinct from the repo's shared
// (common) git-dir. This is false for the main checkout of a repo
// (where git-dir and common-dir are the same), even though the main
// checkout is itself a valid git directory. Intended as a deterministic,
// non-LLM guard checked immediately before any invocation that grants
// filesystem-write access to a directory, so such access can never
// land on the main checkout.
//
// Returns an error if `dir` doesn't exist or isn't a git directory at
// all — callers should treat that as "cannot verify isolation", not
// silently proceed.
IsIsolatedWorktree(ctx context.Context, dir string) (bool, error)
}
Git is the abstraction step-body code calls into. ExecGit is the production impl; tests stub it.
type HookRejectionError ¶
type HookRejectionError struct {
Output string
}
HookRejectionError is returned by Commit when staging succeeded (there was something to commit) but the `git commit` invocation itself failed — in practice this is almost always the target repo's own pre-commit or commit-msg hook rejecting the commit (lint, formatting, secret scanning, file-size caps, etc.). Output holds the hook's raw stdout+stderr so callers can feed it back to the runner as Request.HookFailure (ADR-0075) instead of pausing for a human on every rejection.
func (*HookRejectionError) Error ¶
func (e *HookRejectionError) Error() string