git

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func NewExec(authorName, authorEmail string) *ExecGit

NewExec returns an ExecGit. Both Author fields are optional; if unset, commits inherit the host's `user.name` / `user.email` from .gitconfig.

func (*ExecGit) Commit

func (g *ExecGit) Commit(ctx context.Context, dir, message string) error

func (*ExecGit) ConflictedFiles

func (g *ExecGit) ConflictedFiles(ctx context.Context, dir string) ([]string, error)

func (*ExecGit) DiffShortstat

func (g *ExecGit) DiffShortstat(ctx context.Context, dir, baseBranch string) (string, error)

func (*ExecGit) EnsureBranch

func (g *ExecGit) EnsureBranch(ctx context.Context, dir, baseRepo, baseBranch, branchName string) error

func (*ExecGit) HardReset

func (g *ExecGit) HardReset(ctx context.Context, dir, baseBranch string) error

func (*ExecGit) HasChanges

func (g *ExecGit) HasChanges(ctx context.Context, dir string) (bool, error)

func (*ExecGit) HasWorkBeyondBase

func (g *ExecGit) HasWorkBeyondBase(ctx context.Context, dir, baseBranch string) (bool, error)

func (*ExecGit) IsIsolatedWorktree

func (g *ExecGit) IsIsolatedWorktree(ctx context.Context, dir string) (bool, error)

func (*ExecGit) Push

func (g *ExecGit) Push(ctx context.Context, dir, branchName string) error

func (*ExecGit) RemoveWorktree

func (g *ExecGit) RemoveWorktree(ctx context.Context, baseRepo, dir string) error

func (*ExecGit) SyncWithBase

func (g *ExecGit) SyncWithBase(ctx context.Context, dir, baseBranch string) error

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

Jump to

Keyboard shortcuts

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