repository

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 23 Imported by: 1

Documentation

Overview

Package repository provides git repository abstraction and bulk operations.

This package implements the core repository operations including cloning, fetching, pulling, pushing, and status checking. It supports both single repository and bulk (multi-repository) operations.

Bulk Operations

By default, gz-git operates in bulk mode, scanning directories and processing multiple repositories in parallel.

Defaults:

  • Scan depth: 1 (current directory + 1 level)
  • Parallel jobs: 10

Features

  • Repository state management
  • Bulk clone/fetch/pull/push
  • Status checking with divergence detection
  • Branch operations across repositories
  • Refspec support for push

Usage

client := repository.NewClient()
results, err := client.BulkStatus(ctx, "/path/to/workspace", repository.BulkOptions{
    ScanDepth: 1,
    Parallel:  10,
})

Index

Constants

View Source
const (
	// Blockers.
	CodeRebaseInProgress = "REBASE_IN_PROGRESS"
	CodeMergeInProgress  = "MERGE_IN_PROGRESS"
	CodeConflicts        = "CONFLICTS_PRESENT"
	CodeScanError        = "SCAN_ERROR"

	// Branch position.
	CodeDetachedHead               = "DETACHED_HEAD"
	CodeNoUpstream                 = "NO_UPSTREAM"
	CodeUpstreamTargetsIntegration = "UPSTREAM_TARGETS_INTEGRATION_BRANCH"
	CodeUpstreamDiverged           = "UPSTREAM_DIVERGED"
	CodeUnpushedCommits            = "UNPUSHED_COMMITS"
	CodeUpstreamBehind             = "UPSTREAM_BEHIND"
	CodeBranchBehindBase           = "BRANCH_BEHIND_BASE"
	CodeBaseUnresolved             = "BASE_UNRESOLVED"
	CodeWorkOnBaseBranch           = "WORK_ON_BASE_BRANCH"
	CodeMergedNotReclaimed         = "MERGED_BRANCH_NOT_RECLAIMED"
	CodeRemoteBotReclaimable       = "REMOTE_BOT_BRANCH_RECLAIMABLE"
	CodeRemoteBotSuperseded        = "REMOTE_BOT_BRANCH_SUPERSEDED"
	CodeRemoteBotPending           = "REMOTE_BOT_BRANCH_PENDING"

	// Local state.
	CodeDirtyWorktree       = "DIRTY_WORKTREE"
	CodeWorktreePrunable    = "WORKTREE_PRUNABLE"
	CodeWorktreeReclaimable = "WORKTREE_RECLAIMABLE"
	CodeStaleStash          = "STALE_STASH"
)

Finding codes. These are the contract: an agent dispatches on Code, not on Message. Message wording may change between releases; a code never changes meaning, and a retired code is not reused.

Blocker codes describe states in which the rest of the audit cannot be trusted — a repository mid-rebase has a HEAD that is a transient artifact of the rebase, so its ahead/behind counts describe nothing a user would act on. They set AuditRepo.Complete to false.

View Source
const (
	SeverityBlocker = "blocker"
	SeverityWarn    = "warn"
	SeverityInfo    = "info"
)

Severity levels.

View Source
const (
	ActionResolveManually    = "resolve-manually"
	ActionRebaseOntoBase     = "rebase-onto-base"
	ActionPush               = "push"
	ActionPull               = "pull"
	ActionSetUpstream        = "set-upstream"
	ActionCheckoutBranch     = "checkout-branch"
	ActionMoveWorkToBranch   = "move-work-to-branch"
	ActionCommitOrDiscard    = "commit-or-discard"
	ActionDeleteBranch       = "delete-branch"
	ActionDeleteRemoteBranch = "delete-remote-branch"
	ActionPruneWorktrees     = "prune-worktrees"
	ActionRemoveWorktree     = "remove-worktree"
	ActionReviewStash        = "review-stash"
	ActionConfigureBase      = "configure-base"
)

Remediation action verbs. An agent switches on Action to decide which of its own capabilities to invoke; Command is the concrete argv for the simple case where it just wants to run git.

View Source
const (
	BotKindDependabot    = "dependabot"
	BotKindRenovate      = "renovate"
	BotKindGitHubActions = "github-actions"
)

Bot branch prefixes used by Dependabot, Renovate, and github-actions. Matching is on the branch name after a leading remote prefix is stripped.

View Source
const (
	StatusBranchesListed = "listed"
	StatusNoBranches     = "no-branches"
)

Status constants for branch list operations.

View Source
const (
	StatusCleanedUp    = "cleaned-up"
	StatusNothingToDo  = "nothing-to-do"
	StatusWouldCleanup = "would-cleanup"
)

Status constants for cleanup operations.

View Source
const (
	StatusExecOK     = "success"
	StatusExecFailed = "error"
	StatusWouldExec  = "would-exec"
)

Status constants for bulk exec.

View Source
const (
	StatusStashed     = "stashed"
	StatusPopped      = "popped"
	StatusWouldStash  = "would-stash"
	StatusWouldPop    = "would-pop"
	StatusNoChanges   = "no-changes"
	StatusNoStash     = "no-stash"
	StatusHasStash    = "has-stash"
	StatusListedStash = "listed"
)

Status constants for stash operations.

View Source
const (
	StatusTagCreated     = "tag-created"
	StatusTagPushed      = "tag-pushed"
	StatusTagExists      = "tag-exists"
	StatusWouldCreateTag = "would-create"
	StatusWouldPushTag   = "would-push"
	StatusNoTags         = "no-tags"
	StatusHasTags        = "has-tags"
)

Status constants for tag operations.

View Source
const (
	// DefaultLocalScanDepth is the default directory depth to scan for repositories.
	// maxDepth=1 means scan only direct children of root (depth 0 -> depth 1)
	// maxDepth=2 means scan root + 2 levels (depth 0 -> depth 1 -> depth 2).
	DefaultLocalScanDepth = 1

	// DefaultLocalParallel is the default number of parallel workers for local Git operations.
	DefaultLocalParallel = 10
)

=== Group 1: Local Repository Operations === Used for local Git operations: status, fetch, pull, push, switch, stash, tag, branch, etc. These operations don't have rate limits, so higher parallelism is safe.

View Source
const (
	// DefaultCloneParallel is the default number of parallel clone operations.
	DefaultCloneParallel = 10

	// DefaultCloneRetries is the default number of retry attempts for failed clone operations.
	DefaultCloneRetries = 3
)

=== Group 3: Clone Operations === Used for bulk clone operations (workspace sync, clone command).

View Source
const (
	// DefaultBulkParallel is an alias for DefaultLocalParallel.
	//
	// Deprecated: Use DefaultLocalParallel for local operations or DefaultForgeParallel for API operations.
	DefaultBulkParallel = DefaultLocalParallel

	// DefaultBulkMaxDepth is an alias for DefaultLocalScanDepth.
	//
	// Deprecated: Use DefaultLocalScanDepth instead.
	DefaultBulkMaxDepth = DefaultLocalScanDepth
)

=== Legacy Aliases (for backward compatibility) === These maintain compatibility with existing code that uses the old constant names. New code should prefer the group-specific constants above.

View Source
const (
	// StatusError indicates an error occurred during the operation.
	StatusError = "error"

	// StatusSkipped indicates the repository was skipped (e.g., uncommitted changes).
	StatusSkipped = "skipped"

	// StatusUpToDate indicates the repository is already up to date (no changes needed).
	// This is the unified status for "no changes" across fetch, pull, and push.
	StatusUpToDate = "up-to-date"

	// StatusUpdated indicates the repository was successfully updated (generic).
	StatusUpdated = "updated"

	// StatusSuccess indicates the operation completed successfully (generic).
	// Prefer specific statuses (StatusFetched, StatusPulled, StatusPushed) when possible.
	StatusSuccess = "success"

	// StatusFetched indicates commits were successfully fetched from remote.
	StatusFetched = "fetched"

	// StatusPulled indicates commits were successfully pulled from remote.
	StatusPulled = "pulled"

	// StatusPushed indicates commits were successfully pushed to remote.
	StatusPushed = "pushed"

	// StatusCloned indicates the repository was successfully cloned.
	StatusCloned = "cloned"

	// StatusRebased indicates the repository was successfully rebased.
	StatusRebased = "rebased"

	// StatusReset indicates the repository was successfully reset.
	StatusReset = "reset"

	// StatusNoRemote indicates no remote is configured for the repository.
	StatusNoRemote = "no-remote"

	// StatusNoUpstream indicates no upstream branch is configured.
	StatusNoUpstream = "no-upstream"

	// StatusNoCommits indicates the repository has no commits at all, so there
	// is nothing that could be pushed.
	//
	// It is separate from StatusError because the two are cleared by different
	// actions and only one of them is the user's to clear. Without this status a
	// `--refspec HEAD:master` run reports an empty repository as "source branch
	// 'HEAD' does not exist", which is textually true and diagnostically wrong:
	// it reads as "you named a branch that isn't here" when the fact is "nothing
	// is here yet". The first is a typo in the command; the second is a
	// repository that was created and never used. A person scanning an error
	// list for real push failures has to open the second kind to find out it was
	// never a target, and in a bulk run over a hundred repositories that cost is
	// paid on every run.
	StatusNoCommits = "no-commits"

	// StatusBaseBlocked indicates the repository's own branch updated cleanly
	// but its local base ref diverged from the remote and was left untouched.
	// It is a distinct status rather than a note on a success because a base ref
	// holding commits the remote has never seen is exactly the state a user
	// needs to look at, and a row that renders as "up-to-date" is not looked at.
	StatusBaseBlocked = "base-blocked"

	// StatusBaseFailed indicates the base sync could not run to a decision, as
	// opposed to reaching one the user must act on. It is separate from
	// StatusBaseBlocked so the blocked list stays a list of repositories that
	// need a person: a row saying "git failed here" is real, but nobody clears
	// it by pushing commits, and counting it alongside the ones they can clear
	// makes the count lie about how much work is outstanding.
	StatusBaseFailed = "base-failed"

	// StatusBaseSynced indicates a local base ref was advanced to its remote.
	// Moving a ref is a write to the user's repository, and a write reported as
	// "up-to-date" is a write the user never sees. This status exists so the row
	// survives the renderer's issue filter and can say which ref moved and by how
	// much, rather than repairing refs invisibly.
	StatusBaseSynced = "base-synced"

	// StatusWouldUpdate indicates the operation would update (dry-run mode).
	StatusWouldUpdate = "would-update"

	// StatusWouldFetch indicates the operation would fetch (dry-run mode).
	StatusWouldFetch = "would-fetch"

	// StatusWouldPull indicates the operation would pull (dry-run mode).
	StatusWouldPull = "would-pull"

	// StatusWouldPush indicates the operation would push (dry-run mode).
	StatusWouldPush = "would-push"

	// StatusClean indicates the repository working tree is clean.
	StatusClean = "clean"

	// StatusDirty indicates the repository has uncommitted changes.
	StatusDirty = "dirty"

	// StatusConflict indicates the repository has merge/rebase conflicts.
	StatusConflict = "conflict"

	// StatusRebaseInProgress indicates a rebase operation is in progress.
	StatusRebaseInProgress = "rebase-in-progress"

	// StatusMergeInProgress indicates a merge operation is in progress.
	StatusMergeInProgress = "merge-in-progress"

	// StatusSwitched indicates the branch was successfully switched.
	StatusSwitched = "switched"

	// StatusAlreadyOnBranch indicates the repository is already on the target branch.
	StatusAlreadyOnBranch = "already-on-branch"

	// StatusBranchCreated indicates a new branch was created and switched to.
	StatusBranchCreated = "branch-created"

	// StatusWouldSwitch indicates the operation would switch (dry-run mode).
	StatusWouldSwitch = "would-switch"

	// StatusBranchNotFound indicates the target branch was not found.
	StatusBranchNotFound = "branch-not-found"

	// StatusAuthRequired indicates the operation failed due to authentication requirements.
	// This typically occurs when HTTPS credentials are not configured or have expired.
	StatusAuthRequired = "auth-required"

	// StatusBlocked indicates the operation was refused by a configured policy
	// rather than by git or the remote.
	StatusBlocked = "blocked"

	// StatusCleaned indicates untracked/ignored files were removed.
	StatusCleaned = "cleaned"

	// StatusWouldClean indicates files would be removed (dry-run mode).
	StatusWouldClean = "would-clean"

	// StatusNothingToClean indicates no untracked/ignored files to remove.
	StatusNothingToClean = "nothing-to-clean"
)

Status constants for bulk operations. These provide consistent status values across all bulk operations.

Status Display Guidelines:

  • Changes occurred: "N↓ fetched", "N↓ pulled", "N↑ pushed"
  • No changes: "up-to-date" (unified across all commands)
  • Icons: ✓ (changes), = (no changes), ✗ (error), ⚠ (warning), ⊘ (skipped)
View Source
const AuditSchema = "gz-git.info.audit/v1"

AuditSchema versions the machine-readable contract. Consumers should refuse input whose schema they do not recognize rather than guess at field meanings.

View Source
const (
	// DefaultForgeParallel is the default number of parallel workers for forge API operations.
	// Set lower than local operations to respect API rate limits.
	DefaultForgeParallel = 4
)

=== Group 2: Remote/Forge API Operations === Used for GitHub/GitLab/Gitea API calls (forge from, config generate, etc.) Lower parallelism to respect rate limits and avoid API throttling.

View Source
const DefaultRemoteName = "origin"

DefaultRemoteName is the remote assumed when a repository does not say which one it means. It is exported because pkg/branch needs the same assumption to resolve a declared canonical branch to a remote-tracking ref.

View Source
const StrandedStashAge = 7 * 24 * time.Hour

StrandedStashAge is how long a stash may sit before it stops counting as work in progress. A handoff cycle runs in a day or so, so an entry that survived a week survived several of them without anyone reaching for it.

It lives here rather than in either caller because "handoff check" and "doctor" have to agree: one calling a stash stranded while the other calls it normal is worse than either threshold on its own.

Variables

View Source
var DefaultAutofixPolicy = map[string]bool{
	CodeUpstreamBehind:      true,
	CodeBranchBehindBase:    true,
	CodeMergedNotReclaimed:  true,
	CodeWorktreePrunable:    true,
	CodeWorktreeReclaimable: true,
}

DefaultAutofixPolicy answers, per finding code, whether an agent may apply the remediation without asking.

There is one table, not a set of named tiers. A tier name ("conservative", "aggressive") is a label a user has to decode into behavior before they can trust it, and it forces every future code into someone else's idea of a risk bucket. A per-code boolean says exactly what it does, and a project that disagrees about one code overrides that one code instead of jumping tiers.

The default set is deliberately narrow: an entry is true only when the remediation is a single command that re-verifies its own precondition at run time, so a stale audit cannot cause it to destroy anything.

  • UPSTREAM_BEHIND `pull --ff-only` refuses to invent a merge commit.
  • BRANCH_BEHIND_BASE `rebase <base>` stops on conflict; ORIG_HEAD holds the pre-rebase tip.
  • MERGED_BRANCH_NOT_RECLAIMED `branch -d` re-checks the merge itself and refuses anything unmerged.
  • WORKTREE_PRUNABLE `worktree prune` removes bookkeeping for directories that are already gone.
  • WORKTREE_RECLAIMABLE `worktree remove` refuses on modified or untracked files, so it cannot discard anything not already in the base.

Everything absent is false. Absence covers three different reasons, all of which reduce to "not unattended": the operation is irreversible and applyAutofixPolicy would refuse anyway (push, commit-or-discard), the repair needs a decision no table can supply (which branch to check out, which base to configure, rebase-or-merge on a divergence), or the remediation only gathers information and repairs nothing (reviewing a stash).

View Source
var ErrAuthRequired = fmt.Errorf("authentication required (credential helper not configured)")

ErrAuthRequired is returned when a git operation fails due to missing credentials.

View Source
var ProtectedBranches = []string{
	"main",
	"master",
	"develop",
	"development",
	"release/*",
	"hotfix/*",
}

ProtectedBranches lists the branch names and patterns that require --force to delete. It is the single source of truth for protected-branch judgment: both the single-repo path (pkg/branch) and the bulk path (this package) resolve protection through IsProtected, so adding a pattern here closes it on every deletion path at once.

View Source
var RetirableTrunkNames = []string{
	"main",
	"master",
	"develop",
	"development",
}

RetirableTrunkNames lists the built-in protected names that a canonical-branch declaration is allowed to overrule.

It is deliberately narrower than ProtectedBranches, and deliberately a list of its own rather than "ProtectedBranches minus a couple of patterns": the two lists answer different questions, and deriving one from the other would widen this one silently the next time a pattern is added above.

release/* and hotfix/* are absent on purpose. They are protected for a different reason than the trunk names are — a release line is *expected* to be an ancestor of the trunk, which is exactly the shape the non-canonical gate looks for, so every merged release branch would qualify. Retiring those is a release-policy decision, not a duplicate-trunk cleanup.

Functions

func AutofixPolicyFrom

func AutofixPolicyFrom(overrides map[string]bool) func(code string) bool

AutofixPolicyFrom builds the policy function EvaluateRepo consults, starting from DefaultAutofixPolicy and applying overrides on top.

An override may enable or disable any code; enabling one still cannot make an irreversible remediation auto-fixable, because applyAutofixPolicy checks reversibility after consulting policy. Configuration therefore adjusts what is permitted, never what is safe.

func BotKind

func BotKind(name string) string

BotKind returns "dependabot", "renovate", "github-actions", or "" if name is not a bot branch.

func ExtractRepoNameFromURL

func ExtractRepoNameFromURL(repoURL string) (string, error)

ExtractRepoNameFromURL extracts the repository name from a Git URL This is useful for determining a default destination directory name

Supports various URL formats: - https://github.com/user/repo.git → repo - git@github.com:user/repo.git → repo - ssh://git@server.com/user/repo.git → repo

Example:

name, err := ExtractRepoNameFromURL("https://github.com/user/my-repo.git")
// name == "my-repo"

func IsBotBranch

func IsBotBranch(name string) bool

IsBotBranch reports whether name is a leftover automation branch. A leading origin/ or other remote prefix is ignored so both dependabot/go_modules/x and origin/dependabot/go_modules/x match.

func IsDryRunStatus

func IsDryRunStatus(status string) bool

IsDryRunStatus returns true if the status indicates a dry-run simulation.

func IsErrorStatus

func IsErrorStatus(status string) bool

IsErrorStatus returns true if the status indicates an error.

func IsMergeInProgress

func IsMergeInProgress(repoPath string) bool

IsMergeInProgress checks if a merge operation is in progress for the given repository path. It checks for the existence of .git/MERGE_HEAD file.

func IsProtected

func IsProtected(name string) bool

IsProtected reports whether name matches a built-in protected branch pattern.

func IsRebaseInProgress

func IsRebaseInProgress(repoPath string) bool

IsRebaseInProgress checks if a rebase operation is in progress for the given repository path. It checks for the existence of .git/rebase-merge or .git/rebase-apply directories.

func IsRemoteHeadRefusal

func IsRemoteHeadRefusal(stderr string) bool

IsRemoteHeadRefusal recognizes the one remote-delete failure that is not a fault to report but a step the operator has not taken yet: the branch is still where the remote's HEAD points. A duplicate default branch is the common shape of the problem --non-canonical exists to clean up, so the raw git text — several lines about receive.denyDeleteCurrent, or a forge's own wording — would bury the only thing worth acting on.

It lives here rather than in pkg/branch because both cleanup paths need the same wording and the dependency runs branch → repository.

func IsRetirableTrunkName

func IsRetirableTrunkName(name string) bool

IsRetirableTrunkName reports whether a declared canonical branch may overrule built-in protection for this name.

name must be the bare branch name, as normalizeCleanupBranch produces. A remote-tracking spelling ("remotes/origin/master", "origin/master") matches nothing here and is therefore not retirable — the safe answer, and one callers may rely on: it is what stops a hand-assembled CleanupReport carrying a qualified name from reaching a delete.

func IsSuccessStatus

func IsSuccessStatus(status string) bool

IsSuccessStatus returns true if the status indicates a successful operation.

func MatchTaskPattern

func MatchTaskPattern(name, pattern string) bool

MatchTaskPattern reports whether name falls inside the task-branch namespace a declared pattern opens. The namespace is the prefix before the first '*', so "dev/*/*/*" matches "dev/a/b/c" and equally "dev/a/b/c/d" (DECISION-004). A trailing-'*' compare on the whole pattern would read the middle stars as literal characters and miss every real task branch.

Pattern "*" (empty prefix) never matches; the loader rejects it.

Ownership lives here rather than in pkg/config because two callers need it and the dependency runs config → branch → repository: pkg/branch cannot import pkg/config without a cycle. This mirrors how IsProtected is owned — the lower package holds the judgment, the higher ones delegate, and neither forks the semantics.

func MatchesAnyTaskPattern

func MatchesAnyTaskPattern(name string, patterns []string) bool

MatchesAnyTaskPattern reports whether name matches any declared pattern.

func NonInteractiveEnv

func NonInteractiveEnv() []string

NonInteractiveEnv returns the environment that disables git credential prompts, so a command against an unreachable or private remote fails with an error instead of blocking on a terminal that may not be there.

It is exported because pkg/branch runs the same kind of best-effort fetch and needs the same protection; sharing the list keeps the two from drifting apart if git ever gains another prompting channel. The returned slice is a copy — callers may append their own entries to it.

func ParseStashDates

func ParseStashDates(output string) (count int, oldest time.Time)

ParseStashDates reads the output of `git stash list --format=%ct` and returns how many entries there are and when the oldest was created.

git lists stashes newest first, but that ordering is a property of the reflog rather than of the dates: `git stash push` on an older base, or a reordering drop, can leave an entry whose date is not where its position suggests. So the oldest is found by comparing, not by taking the last line.

func StashIsStranded

func StashIsStranded(oldest time.Time) bool

StashIsStranded reports whether the oldest entry of a repository's stash has outlived the task that created it. A zero time means there is no stash.

Types

type AuditBase

type AuditBase struct {
	Name   string `json:"name,omitempty"`
	Source string `json:"source"`
	Ahead  int    `json:"ahead"`
	Behind int    `json:"behind"`
}

AuditBase records the resolved integration branch and how it was chosen, so a consumer can weigh a divergence claim against the confidence of the base it was measured from.

type AuditInput

type AuditInput struct {
	Name   string
	Path   string
	Status *RepositoryStatusResult
	Base   BaseBranchInfo

	// Integration* is the repo-root integrationBranch answer used only for the
	// upstream safety finding. It is deliberately separate from Base, whose
	// public meaning remains defaultBranch-relative divergence.
	IntegrationName            string
	IntegrationSource          string
	UpstreamTargetsIntegration bool
	UpstreamRemote             string
	TaskRemoteExists           bool

	// Worktrees are the checkouts other than the main one, with the branch each
	// holds. Branches checked out here are excluded from plain branch deletion:
	// `git branch -d` refuses a branch a worktree is using, so a remediation
	// that ignored this would emit a command that cannot run.
	Worktrees []AuditWorktree

	// PrunableWorktrees are paths git reports as orphaned metadata.
	PrunableWorktrees []string

	// MergedBranches are local branches fully contained in the base branch,
	// excluding the base itself. Verified by ancestry, never inferred from
	// naming or from divergence counts.
	MergedBranches []string

	// RemoteBotMerged are origin remote-tracking bot branches whose tips are
	// ancestors of the base. Names have no origin/ prefix.
	RemoteBotMerged []string

	// RemoteBotSuperseded are origin remote-tracking bot branches that are not
	// ancestors of the base, but whose version target is already satisfied
	// there (equal or newer Go module / Actions pin).
	RemoteBotSuperseded []string

	// RemoteBotPending are origin remote-tracking bot branches that are not
	// ancestors of the base and still newer or not comparable — they may
	// still be an open PR.
	RemoteBotPending []string

	// EnrichErr is a failure from collecting the extra facts above.
	EnrichErr error

	// StaleStashAfter is how old the oldest stash must be to be reported.
	// Zero disables the check.
	StaleStashAfter time.Duration

	// Now is the reference time for age comparisons, injected so results are
	// reproducible in tests.
	Now time.Time

	// AutofixPolicy answers whether an agent may apply a given code
	// unattended. A nil policy means nothing is auto-fixable.
	AutofixPolicy func(code string) bool
}

AuditInput is everything the evaluator needs, already collected. Keeping the evaluation a pure function of this struct is what makes the catalog testable without a git fixture per case.

type AuditRepo

type AuditRepo struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Branch string `json:"branch,omitempty"`

	// Complete reports whether every check ran against trustworthy state.
	// False means findings on this repository are partial, and an agent must
	// resolve the blocker before acting on anything else here — emitting
	// confident numbers derived from a half-finished rebase would be worse
	// than admitting the audit could not run.
	//
	// The wire name keeps the "audit_" prefix the Go field drops: inside
	// AuditRepo the type already supplies that context, but in the emitted
	// document a bare "complete" reads as a property of the repository rather
	// than of the audit that examined it.
	Complete bool `json:"audit_complete"` //nolint:tagliatelle // wire name is part of the published v1 schema

	// IncompleteReason is the code of the blocker that set Complete false.
	IncompleteReason string `json:"incomplete_reason,omitempty"`

	Base     AuditBase `json:"base"`
	Upstream string    `json:"upstream,omitempty"`

	// Worktrees lists the linked checkouts. They are reported even when they
	// produce no finding: a branch checked out elsewhere is not visible in this
	// repository's own status, so without this an agent reading the document
	// would conclude the work does not exist.
	Worktrees []AuditWorktree `json:"worktrees,omitempty"`

	Findings []Finding `json:"findings"`
}

AuditRepo is one repository's verdict.

func EvaluateRepo

func EvaluateRepo(in AuditInput) AuditRepo

EvaluateRepo runs the finding catalog against one repository.

Blockers short-circuit: when the working state is untrustworthy the function returns that single finding with Complete false, rather than appending it to a list of numbers it just declared unreliable. An agent that sees audit_complete=false knows to fix the blocker and re-run, which is a safer instruction than a long list of findings derived from a half-finished rebase.

type AuditResult

type AuditResult struct {
	Schema       string       `json:"schema"`
	Directory    string       `json:"directory"`
	Repositories []AuditRepo  `json:"repositories"`
	Summary      AuditSummary `json:"summary"`
}

AuditResult is the top-level document.

type AuditSummary

type AuditSummary struct {
	Total          int            `json:"total"`
	Complete       int            `json:"complete"`
	Incomplete     int            `json:"incomplete"`
	WithFindings   int            `json:"with_findings"`
	FindingsByCode map[string]int `json:"findings_by_code,omitempty"`
	Blockers       int            `json:"blockers"`
}

AuditSummary lets a caller triage without walking every finding.

func Summarize

func Summarize(repos []AuditRepo) AuditSummary

Summarize aggregates repository verdicts for triage.

type AuditWorktree

type AuditWorktree struct {
	Path string `json:"path"`

	// Branch is empty for a detached worktree.
	Branch string `json:"branch,omitempty"`
}

AuditWorktree is one checkout of the repository other than the main one.

type BaseBranchInfo

type BaseBranchInfo struct {
	// Name is the resolved base branch, or empty when no candidate exists.
	Name string

	// Source explains the resolution: "config.defaultBranch[i]" for the i-th
	// configured candidate that won, "heuristic" for the fallback list, or
	// "none" when nothing matched. Never empty.
	Source string

	// Ahead is the number of commits reachable from HEAD but not from the base
	// (work the base does not have yet). Zero when there is no base.
	Ahead int

	// Behind is the number of commits reachable from the base but not from HEAD
	// (work the base has that HEAD lacks — the rebase/pull signal). Zero when
	// there is no base.
	Behind int

	// SHA is the short hash of the base tip, or empty when there is no base.
	SHA string
}

BaseBranchInfo describes the integration branch a repository's work is meant to land on, and how the current HEAD relates to it.

"Base" is a policy decision, not a git fact: a project declares an ordered set of candidate integration branches (config defaultBranch, e.g. "develop,master") and the first one present locally wins. Source records where the answer came from so a caller can explain it — "master, because config.defaultBranch[1]" reads differently from "master, by heuristic", and a workflow that blocks on the wrong base is worse than one that admits it could not find one.

type BaseDivergence

type BaseDivergence struct {
	// RemoteOnly is the number of commits the remote base has that the local
	// ref lacks — how far the local pointer fell behind.
	RemoteOnly int

	// LocalOnly is the number of commits on the local base ref that the remote
	// base does not have. Non-zero means this is not a fast-forward.
	LocalOnly int

	// Stranded is how many of the LocalOnly commits are reachable from no
	// remote-tracking ref of this remote at all — not from the base, not from a
	// task branch, not from anything that was ever pushed.
	//
	// This is the number that decides whether local-only commits represent real
	// unpushed work or a stale pointer parked on a branch that was pushed
	// elsewhere. A local base sitting on the tip of a task branch that lives on
	// the remote has LocalOnly > 0 and Stranded == 0: the commits exist on
	// origin under another name, so moving the pointer loses no history. A base
	// carrying genuinely unpushed commits has Stranded > 0, and moving the
	// pointer would strand them in the reflog.
	Stranded int
}

BaseDivergence counts how a local base ref and its remote-tracking counterpart differ. The three numbers answer three different questions and the distinction between LocalOnly and Stranded is the whole safety argument.

type BaseSyncAction

type BaseSyncAction string

BaseSyncAction is the decision SyncBase reached for one repository's local base ref. It is reported even when nothing was written, so a dry run and a real run describe themselves in the same vocabulary.

const (
	// BaseSyncUpToDate means the local base ref already equals its
	// remote-tracking ref. Nothing to do.
	BaseSyncUpToDate BaseSyncAction = "up-to-date"

	// BaseSyncFastForward means the local base ref was a strict ancestor of the
	// remote and was advanced to it. No local commit was dropped, because there
	// was no local-only commit to drop.
	BaseSyncFastForward BaseSyncAction = "fast-forward"

	// BaseSyncAdopted means the local base ref held commits the remote base
	// lacks, and the policy still moved the ref to the remote tip. Only reached
	// when decideBaseDivergence judged those commits recoverable.
	BaseSyncAdopted BaseSyncAction = "adopted"

	// BaseSyncBlocked means the local base ref diverged and the policy refused
	// to move it. The ref is left exactly as it was; this is a report, not a
	// failure of the update.
	BaseSyncBlocked BaseSyncAction = "blocked"

	// BaseSyncFailed means the sync could not reach a decision at all — a git
	// command failed, the repository is unreadable. It is deliberately not
	// BaseSyncBlocked. Blocked is a verdict the policy reached on evidence and
	// carries an instruction to the user ("push these commits, then run
	// again"); failed carries none, because nothing was judged. Folding the two
	// together puts rows nobody can act on into the one list whose entire value
	// is that every row needs action.
	BaseSyncFailed BaseSyncAction = "failed"

	// BaseSyncCreated means no local base ref existed and one was created from
	// the remote. Only reachable with BaseSyncOptions.CreateMissing: a
	// repository deliberately kept without a local trunk is a legitimate
	// choice, so materializing one is opt-in rather than a repair.
	BaseSyncCreated BaseSyncAction = "created"

	// BaseSyncSkipped means there was nothing to sync: no base resolved, no
	// remote-tracking counterpart, or the base is the checked-out branch and
	// the normal pull path already owns it.
	BaseSyncSkipped BaseSyncAction = "skipped"
)

type BaseSyncOptions

type BaseSyncOptions struct {
	// Remote is the remote whose base ref is the target. Required.
	Remote string

	// Candidates is the base-branch search order, typically
	// EffectiveConfig.Branch.DefaultBranch. Empty falls back to the heuristic
	// list ResolveBase already uses.
	Candidates []string

	// Fetch refreshes the base's remote-tracking ref before comparing.
	Fetch bool

	// DryRun reports the decision without writing any ref.
	DryRun bool

	// CreateMissing materializes a local base ref from the remote when the
	// repository has none. Off by default: absence of a local trunk is often
	// intentional on a develop-only clone, and creating a branch the user never
	// asked for is a different act than repairing one that fell behind.
	CreateMissing bool
}

BaseSyncOptions configures one SyncBase call.

This is a struct rather than a parameter list because the flags are all booleans: SyncBase(ctx, path, remote, candidates, true, false, true) states nothing about what those positions mean, and the compiler cannot catch two of them being swapped.

type BaseSyncResult

type BaseSyncResult struct {
	// Base is the resolved base branch name, empty when none resolved.
	Base string

	// BaseSource echoes BaseBranchInfo.Source so a caller can explain which
	// policy picked this branch.
	BaseSource string

	// Remote is the remote whose base ref was used as the target.
	Remote string

	// Action is the decision reached.
	Action BaseSyncAction

	// Advanced is how many commits the local base ref gained. Zero for every
	// action except FastForward and Adopted, and zero on a dry run.
	Advanced int

	// Divergence holds the counts the decision was made from.
	Divergence BaseDivergence

	// Reason explains a Skipped or Blocked action in one clause. Empty when the
	// action speaks for itself.
	Reason string

	// Backup is the ref the previous base tip was parked at before an adopt
	// rewound it, empty when nothing was parked. Only Adopted sets it.
	Backup string

	// DryRun echoes BaseSyncOptions.DryRun.
	//
	// Advanced is zero both on a dry run and on a real adopt that only rewinds,
	// so a renderer cannot tell "nothing happened yet" from "a ref moved
	// backwards" without being told which run this was.
	DryRun bool
}

BaseSyncResult reports what SyncBase decided and did for one repository.

type BranchConfig

type BranchConfig struct {
	// Remote is the default remote for this branch.
	Remote string

	// Merge is the default merge ref for this branch.
	Merge string

	// Rebase indicates if rebase should be used instead of merge.
	Rebase bool
}

BranchConfig represents branch-specific configuration.

type BranchInfo

type BranchInfo struct {
	Name     string // Branch name
	SHA      string // Commit SHA
	IsHead   bool   // Currently checked out
	IsRemote bool   // Remote branch
	Upstream string // Upstream branch (if set)
	AheadBy  int    // Commits ahead of upstream
	BehindBy int    // Commits behind upstream
}

BranchInfo represents basic branch information for bulk operations.

type BulkBranchListOptions

type BulkBranchListOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// All includes remote branches (-a/--all)
	All bool

	// Merged shows only merged branches
	Merged bool

	// Unmerged shows only unmerged branches
	Unmerged bool

	// IncludeSubmodules includes git submodules in the scan
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkBranchListOptions configures bulk branch list operations.

type BulkBranchListResult

type BulkBranchListResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryBranchListResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TotalBranchCount is the total number of branches across all repos
	TotalBranchCount int

	// TotalLocalCount is the total number of local branches
	TotalLocalCount int

	// TotalRemoteCount is the total number of remote branches
	TotalRemoteCount int
}

BulkBranchListResult contains the results of a bulk branch list operation.

type BulkCleanOptions

type BulkCleanOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual deletion (default: true)
	DryRun bool

	// RemoveDirectories also removes untracked directories (-d flag)
	RemoveDirectories bool

	// RemoveIgnored removes ignored files in addition to untracked (-x flag)
	RemoveIgnored bool

	// OnlyIgnored removes only ignored files, not untracked (-X flag)
	OnlyIgnored bool

	// ExcludePatterns are patterns to exclude from cleaning (-e flag)
	ExcludePatterns []string

	// IncludeSubmodules includes git submodules in the scan (default: false)
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkCleanOptions configures bulk git clean operations.

type BulkCleanResult

type BulkCleanResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryCleanResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TotalFiles is the total number of files removed/would-remove across all repos
	TotalFiles int
}

BulkCleanResult contains the results of a bulk clean operation.

type BulkCleanupOptions

type BulkCleanupOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// IncludeMerged includes fully merged branches
	IncludeMerged bool

	// IncludeStale includes stale branches (no recent activity)
	IncludeStale bool

	// IncludeGone includes gone branches (remote deleted)
	IncludeGone bool

	// IncludeSuperseded includes unmerged bot remotes whose version target
	// is already satisfied on the base. Only bot names are considered.
	IncludeSuperseded bool

	// IncludeNonCanonical retires branches that duplicate the repository's
	// declared canonical branch. It does nothing without CanonicalResolver.
	IncludeNonCanonical bool

	// CanonicalResolver resolves one repository's declared canonical branch and
	// task-branch allow-list from its .gz-git.yaml.
	//
	// It is injected rather than called directly because the declaration is
	// owned by pkg/config, and pkg/config imports this package — the dependency
	// cannot run the other way. A nil resolver disables the classification
	// entirely, which is the safe default for every existing caller.
	CanonicalResolver func(ctx context.Context, repoPath string) (canonical string, taskPatterns []string, err error)

	// StaleThreshold is the threshold for stale branches (default: 30 days)
	StaleThreshold time.Duration

	// BaseBranch is the base branch for merge detection (default: auto-detect)
	BaseBranch string

	// DeleteRemote also deletes remote branches
	DeleteRemote bool

	// RemoteDeleteGuard authorizes a remote branch deletion for one repository.
	// It is injected by the command boundary because workspace access policy is
	// outside this package. A nil guard preserves existing read-write behavior.
	RemoteDeleteGuard func(ctx context.Context, repoPath string) error

	// BotsOnly restricts candidates to Dependabot/Renovate/github-actions
	// prefixes. It is a filter, not a cleanup type.
	BotsOnly bool

	// ProtectPatterns are additional patterns to protect from deletion
	ProtectPatterns []string

	// IncludeSubmodules includes git submodules in the scan (default: false)
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkCleanupOptions configures bulk branch cleanup operations.

type BulkCleanupResult

type BulkCleanupResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryCleanupResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TotalBranchesDeleted is the total number of branches deleted across all repos
	TotalBranchesDeleted int

	// TotalBranchesFailed is the total number of branches the run tried to
	// delete and could not.
	TotalBranchesFailed int

	// TotalBranchesAnalyzed is the total number of branches analyzed
	TotalBranchesAnalyzed int
}

BulkCleanupResult contains the results of a bulk cleanup operation.

type BulkCloneOptions

type BulkCloneOptions struct {
	// URLs is the list of repository URLs to clone.
	URLs []string

	// Directory is the base directory for cloning (default: current directory).
	Directory string

	// Structure determines directory organization (flat or user).
	Structure DirectoryStructure

	// Strategy determines how to handle existing repositories.
	// Valid values: "skip" (default), "pull", "fetch", "force".
	Strategy UpdateStrategy

	// Branch is the branch to checkout after cloning.
	Branch string

	// Depth limits clone depth (0 = full clone).
	Depth int

	// SingleBranch clones only the target branch history (--single-branch).
	SingleBranch bool

	// Recursive initializes and clones submodules (--recurse-submodules).
	Recursive bool

	// Parallel is the number of concurrent operations.
	Parallel int

	// DryRun shows what would be done without doing it.
	DryRun bool

	// Verbose enables detailed output.
	Verbose bool

	// Logger for operation feedback.
	Logger Logger

	// ProgressCallback is called during bulk operations.
	ProgressCallback func(current, total int, repo string)
}

BulkCloneOptions configures the bulk clone operation.

type BulkCloneResult

type BulkCloneResult struct {
	// TotalRequested is the number of URLs requested.
	TotalRequested int

	// TotalCloned is the number successfully cloned.
	TotalCloned int

	// TotalUpdated is the number successfully updated (when --update is used).
	TotalUpdated int

	// TotalSkipped is the number skipped (already exists, no --update).
	TotalSkipped int

	// TotalFailed is the number that failed.
	TotalFailed int

	// Duration is the total operation time.
	Duration time.Duration

	// Repositories contains individual results.
	Repositories []RepositoryCloneResult

	// Summary contains counts by status.
	Summary map[string]int
}

BulkCloneResult contains the result of a bulk clone operation.

type BulkCommitOptions

type BulkCommitOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 1)
	MaxDepth int

	// DryRun shows what would be committed without actually committing
	DryRun bool

	// Message is a common message for all repositories (overrides auto-generation)
	Message string

	// Yes auto-approves without confirmation
	Yes bool

	// IncludePattern is a regex pattern to include repositories
	IncludePattern string

	// ExcludePattern is a regex pattern to exclude repositories
	ExcludePattern string

	// IncludeSubmodules includes git submodules in the scan
	IncludeSubmodules bool

	// AllowConflicted commits repositories that still have unmerged paths.
	// Off by default: `git add -A` marks conflicts as resolved, so committing
	// them writes conflict markers into history irreversibly.
	AllowConflicted bool

	// Verbose enables detailed logging
	Verbose bool

	// Logger for progress logging
	Logger Logger

	// ProgressCallback is called for each repository processed
	ProgressCallback func(current, total int, repo string)

	// MessageGenerator generates commit messages for repositories
	// If nil, a simple default message is generated
	MessageGenerator func(ctx context.Context, repoPath string, files []string) (string, error)
}

BulkCommitOptions configures bulk commit operations.

type BulkCommitResult

type BulkCommitResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalDirty is the number of repositories with uncommitted changes
	TotalDirty int

	// TotalCommitted is the number of repositories successfully committed
	TotalCommitted int

	// TotalFailed is the number of repositories that failed to commit
	TotalFailed int

	// TotalSkipped is the number of repositories skipped (clean or excluded)
	TotalSkipped int

	// TotalConflicted is the number of repositories left uncommitted because
	// they still have unmerged paths
	TotalConflicted int

	// Repositories contains individual repository results
	Repositories []RepositoryCommitResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkCommitResult contains the results of a bulk commit operation.

type BulkDiffOptions

type BulkDiffOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 1)
	MaxDepth int

	// Staged shows only staged changes (git diff --cached)
	Staged bool

	// IncludeUntracked includes untracked files in the output
	IncludeUntracked bool

	// ContextLines is the number of context lines around changes (default: 3)
	ContextLines int

	// MaxDiffSize limits the diff size per repository in bytes (default: 100KB)
	MaxDiffSize int

	// IncludePattern is a regex pattern to include repositories
	IncludePattern string

	// ExcludePattern is a regex pattern to exclude repositories
	ExcludePattern string

	// IncludeSubmodules includes git submodules in the scan
	IncludeSubmodules bool

	// Verbose enables detailed logging
	Verbose bool

	// Logger for progress logging
	Logger Logger

	// ProgressCallback is called for each repository processed
	ProgressCallback func(current, total int, repo string)
}

BulkDiffOptions configures bulk diff operations.

type BulkDiffResult

type BulkDiffResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalWithChanges is the number of repositories with changes
	TotalWithChanges int

	// TotalClean is the number of repositories without changes
	TotalClean int

	// Repositories contains individual repository results
	Repositories []RepositoryDiffResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkDiffResult contains the results of a bulk diff operation.

type BulkExecOptions

type BulkExecOptions struct {
	Directory         string
	Parallel          int
	MaxDepth          int
	DryRun            bool
	IncludeSubmodules bool
	IncludePattern    string
	ExcludePattern    string
	Logger            Logger
	ProgressCallback  func(current, total int, repo string)

	// Command is argv[0]; Args are remaining argv elements. Never passed through a shell.
	Command string
	Args    []string

	// Env extra environment variables (merged with process env). Values for
	// GZ_REPO_NAME / GZ_REPO_PATH are set per-repo automatically.
	// Timeout is the per-repository command deadline (0 = no limit).
	Timeout time.Duration

	// FailFast cancels remaining work after the first non-zero exit.
	FailFast bool

	// OutputTailMax is max bytes of combined stdout/stderr retained per repo (default 4KiB).
	OutputTailMax int
}

BulkExecOptions configures bulk arbitrary-command execution across repositories.

type BulkExecResult

type BulkExecResult struct {
	TotalScanned   int
	TotalProcessed int
	Repositories   []RepositoryExecResult
	Duration       time.Duration
	Summary        map[string]int
	Command        string
	Args           []string
}

BulkExecResult aggregates bulk exec results.

type BulkFetchOptions

type BulkFetchOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// AllRemotes fetches from all remotes (default: origin only)
	AllRemotes bool

	// Prune removes remote-tracking branches that no longer exist
	Prune bool

	// Tags fetches all tags from remote
	Tags bool

	// IncludeSubmodules includes git submodules in the scan (default: false)
	// When false, only scans for independent nested repositories
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkFetchOptions configures bulk repository fetch operations.

type BulkFetchResult

type BulkFetchResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryFetchResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkFetchResult contains the results of a bulk fetch operation.

type BulkPullOptions

type BulkPullOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// Strategy defines how to merge changes (merge, rebase, ff-only)
	Strategy string

	// Prune removes remote-tracking branches that no longer exist
	Prune bool

	// Tags fetches all tags from remote
	Tags bool

	// Stash automatically stashes local changes before pull
	Stash bool

	// IncludeSubmodules includes git submodules in the scan (default: false)
	// When false, only scans for independent nested repositories
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkPullOptions configures bulk repository pull operations.

type BulkPullResult

type BulkPullResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryPullResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkPullResult contains the results of a bulk pull operation.

type BulkPushOptions

type BulkPushOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// Force forces the push (use with caution)
	Force bool

	// SetUpstream sets upstream for new branches
	SetUpstream bool

	// Tags pushes all tags
	Tags bool

	// Refspec is a custom refspec for branch mapping (e.g., "develop:master")
	Refspec string

	// Remotes is a list of remotes to push to (empty = use origin)
	Remotes []string

	// AllRemotes pushes to all configured remotes
	AllRemotes bool

	// IgnoreDirty skips dirty status check after push (useful for CI/CD)
	IgnoreDirty bool

	// PushAccess optionally decides whether a repository may be pushed. It is
	// evaluated before the repository is opened or any remote is contacted.
	// A denied repository is reported as skipped and the rest of the batch
	// continues.
	PushAccess func(repoPath string) (allowed bool, reason string, err error)

	// Policy restricts which branches may be pushed to and how. A nil policy
	// permits every push. Repositories it refuses are reported as StatusBlocked
	// and the rest of the batch still runs.
	Policy *PushPolicy

	// Identity names this machine and agent, so a force push can tell its own
	// commits from another writer's. An unnamed identity disables that check:
	// a machine that cannot say who it is cannot say who anyone else is.
	Identity identity.Identity

	// IncludeSubmodules includes git submodules in the scan (default: false)
	// When false, only scans for independent nested repositories
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkPushOptions configures bulk repository push operations.

type BulkPushResult

type BulkPushResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryPushResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkPushResult contains the results of a bulk push operation.

type BulkRepoResult

type BulkRepoResult interface {
	GetStatus() string
	GetPath() string
	GetMessage() string
	GetError() error
}

BulkRepoResult is the shared surface for bulk operation per-repo results. Command renderers consume this interface so display/JSON logic stays in one place.

type BulkStashOptions

type BulkStashOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Operation is the stash operation: "save", "list", "pop", "apply"
	Operation string

	// Message is the stash message (for save operation)
	Message string

	// IncludeUntracked includes untracked files (for save operation)
	IncludeUntracked bool

	// IncludeSubmodules includes git submodules in the scan
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// OnlyDirty only processes repositories with uncommitted changes (for save)
	OnlyDirty bool

	// OnlyWithStash only processes repositories with existing stashes (for pop/list)
	OnlyWithStash bool

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkStashOptions configures bulk stash operations.

type BulkStashResult

type BulkStashResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryStashResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TotalStashCount is the total number of stashes affected
	TotalStashCount int
}

BulkStashResult contains the results of a bulk stash operation.

type BulkStatusOptions

type BulkStatusOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// Verbose enables detailed logging for all repositories (including clean ones)
	Verbose bool

	// IncludeSubmodules includes git submodules in the scan (default: false)
	// When false, only scans for independent nested repositories
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkStatusOptions configures bulk repository status check operations.

type BulkStatusResult

type BulkStatusResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryStatusResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkStatusResult contains the results of a bulk status operation.

type BulkSwitchOptions

type BulkSwitchOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Branch is the target branch to switch to (required)
	Branch string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 1)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// Create creates the branch if it doesn't exist
	Create bool

	// Force forces the switch even with uncommitted changes (dangerous)
	Force bool

	// IncludeSubmodules includes git submodules in the scan (default: false)
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkSwitchOptions configures bulk repository branch switch operations.

type BulkSwitchResult

type BulkSwitchResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositorySwitchResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TargetBranch is the branch that was switched to
	TargetBranch string
}

BulkSwitchResult contains the results of a bulk switch operation.

type BulkTagOptions

type BulkTagOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Operation is the tag operation: "create", "list", "push", "status"
	Operation string

	// TagName is the tag name (for create operation)
	TagName string

	// Message is the tag message (creates annotated tag)
	Message string

	// Force overwrites existing tags
	Force bool

	// PushAll pushes all tags (for push operation)
	PushAll bool

	// IncludeSubmodules includes git submodules in the scan
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)
}

BulkTagOptions configures bulk tag operations.

type BulkTagResult

type BulkTagResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryTagResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int

	// TotalTagCount is the total number of tags affected/found
	TotalTagCount int
}

BulkTagResult contains the results of a bulk tag operation.

type BulkUpdateOptions

type BulkUpdateOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// Parallel is the number of concurrent workers (default: 10)
	Parallel int

	// MaxDepth is the maximum directory depth to scan (default: 10)
	MaxDepth int

	// DryRun performs simulation without actual changes
	DryRun bool

	// Verbose enables detailed logging
	Verbose bool

	// NoFetch skips fetching from remote
	NoFetch bool

	// IncludeSubmodules includes git submodules in the scan (default: false)
	// When false, only scans for independent nested repositories
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger

	// ProgressCallback is called for each processed repository
	ProgressCallback func(current, total int, repo string)

	// SyncBase additionally fast-forwards each repository's local base ref to
	// its remote-tracking counterpart, even when the base is not checked out.
	// Off by default: it writes to a ref the user did not ask about.
	SyncBase bool

	// BaseCandidates is the ordered integration-branch list handed to
	// ResolveBase, normally EffectiveConfig.Branch.DefaultBranch. Empty lets
	// ResolveBase fall back to its own heuristic rather than this option
	// inventing an order.
	BaseCandidates []string

	// CreateMissingBase creates a local base ref from the remote in
	// repositories that have none. Requires SyncBase. Off by default and kept
	// separate from it because the two are different acts: SyncBase repairs a
	// pointer the repository already has, this one adds a branch the user never
	// created.
	CreateMissingBase bool
}

BulkUpdateOptions configures bulk repository update operations.

type BulkUpdateResult

type BulkUpdateResult struct {
	// TotalScanned is the number of repositories found
	TotalScanned int

	// TotalProcessed is the number of repositories processed
	TotalProcessed int

	// Repositories contains individual repository results
	Repositories []RepositoryUpdateResult

	// Duration is the total operation time
	Duration time.Duration

	// Summary contains status counts
	Summary map[string]int
}

BulkUpdateResult contains the results of a bulk update operation.

type ChangeEntry

type ChangeEntry struct {
	// Path is the repository-relative path as it exists on disk: never quoted,
	// never a collapsed directory.
	Path string

	// OldPath is the source path of a rename or copy, empty otherwise.
	OldPath string

	// Status is the normalized single-letter code (M, A, D, R, C, ?).
	Status string

	// Staged means the index differs from HEAD for this path.
	Staged bool

	// Untracked means the path is not in the index at all.
	Untracked bool

	// Conflicted means the path has unmerged index stages.
	Conflicted bool
}

ChangeEntry is one path in a change set.

type ChangeScope

type ChangeScope string

ChangeScope names the two-endpoint comparison that defines a change set.

Before this type existed, `diff` and `commit` disagreed about what "changed" meant and neither said so: diff compared worktree against the index, commit hand-assembled a union of both sides, and `executeCommit` then ran `git add -A`, whose actual scope (HEAD against the worktree, untracked included) was computed nowhere at all.

const (
	// ScopeHead compares HEAD against the working tree including untracked
	// files. This is the scope `git add -A && git commit` actually records, and
	// therefore the default for both diff and commit.
	ScopeHead ChangeScope = "head"

	// ScopeStagedOnly compares HEAD against the index (`git diff --cached`).
	ScopeStagedOnly ChangeScope = "staged"

	// ScopeWorktreeOnly compares the index against the working tree
	// (`git diff`). This was diff's previous, undeclared behavior, which is why
	// a fully staged repository reported files but an empty diff body.
	ScopeWorktreeOnly ChangeScope = "worktree"
)

type ChangeSet

type ChangeSet struct {
	Entries []ChangeEntry

	// TrackedCount counts entries git already knows about; UntrackedCount counts
	// the rest. Their sum is len(Entries).
	TrackedCount   int
	UntrackedCount int

	// StagedCount and ConflictCount are subsets of Entries, not of each other.
	StagedCount   int
	ConflictCount int

	// Additions and Deletions are the line counts a commit of this change set
	// would record. Tracked lines come from `git diff --numstat`; untracked files
	// are counted separately, since git cannot diff what is not in the index.
	Additions int
	Deletions int

	// DiffFileCount is how many files actually differ from the scope's base.
	// It is not len(Entries): a path can be listed by status yet be identical to
	// HEAD (index and worktree both changed, canceling out), in which case there
	// is nothing to commit even though the repository looks dirty.
	DiffFileCount int

	Scope ChangeScope
}

ChangeSet is the shared answer to "what changed in this repository", used by both BulkDiff and BulkCommit so the two can no longer disagree.

type ChangedFile

type ChangedFile struct {
	// Path is the file path
	Path string

	// Status is the change status (M=modified, A=added, D=deleted, R=renamed, etc.)
	Status string

	// OldPath is the old path for renamed files
	OldPath string
}

ChangedFile represents a changed file with its status.

type CleanupBranchEntry

type CleanupBranchEntry struct {
	Name     string `json:"name"`
	Reason   string `json:"reason"`
	Location string `json:"location"`
	Kind     string `json:"kind,omitempty"`
	// TargetRef and TargetSHA are set on non-canonical candidates: the ref the
	// ancestry was measured against, spelled in full, and where it pointed. They
	// are the justification for the deletion, and until they were carried here
	// the operator passing --force had the branch name and nothing else.
	TargetRef string `json:"target_ref,omitempty"`
	TargetSHA string `json:"target_sha,omitempty"`
}

CleanupBranchEntry is one branch a cleanup run would delete (dry-run) or did.

type CleanupFailureEntry

type CleanupFailureEntry struct {
	Name     string `json:"name"`
	Reason   string `json:"reason"`
	Location string `json:"location"`
	Error    string `json:"error"`
}

CleanupFailureEntry is one branch a cleanup run attempted and could not delete.

type Client

type Client interface {

	// Open opens an existing Git repository at the specified path.
	// Returns an error if the path is not a valid Git repository.
	Open(ctx context.Context, path string) (*Repository, error)

	// Clone clones a repository from the specified URL to the destination path.
	// Returns the opened repository on success.
	Clone(ctx context.Context, opts CloneOptions) (*Repository, error)

	// CloneOrUpdate intelligently clones a repository if it doesn't exist,
	// or updates it using the specified strategy if it does.
	// This is a high-level convenience method for repository synchronization.
	CloneOrUpdate(ctx context.Context, opts CloneOrUpdateOptions) (*CloneOrUpdateResult, error)

	// BulkUpdate scans for repositories and updates them in parallel.
	// This is useful for updating multiple repositories at once.
	BulkUpdate(ctx context.Context, opts BulkUpdateOptions) (*BulkUpdateResult, error)

	// BulkFetch scans for repositories and fetches them in parallel.
	// This is useful for fetching updates from multiple repositories at once.
	BulkFetch(ctx context.Context, opts BulkFetchOptions) (*BulkFetchResult, error)

	// BulkPull scans for repositories and pulls them in parallel.
	// This is useful for pulling updates (fetch + merge/rebase) from multiple repositories at once.
	BulkPull(ctx context.Context, opts BulkPullOptions) (*BulkPullResult, error)

	// BulkPush scans for repositories and pushes them in parallel.
	// This is useful for pushing local commits from multiple repositories at once.
	BulkPush(ctx context.Context, opts BulkPushOptions) (*BulkPushResult, error)

	// BulkStatus scans for repositories and checks their status in parallel.
	// This is useful for checking the working tree status of multiple repositories at once.
	BulkStatus(ctx context.Context, opts BulkStatusOptions) (*BulkStatusResult, error)

	// BulkSwitch scans for repositories and switches their branches in parallel.
	// This is useful for switching branches across multiple repositories at once.
	BulkSwitch(ctx context.Context, opts BulkSwitchOptions) (*BulkSwitchResult, error)

	// BulkCommit scans for repositories with uncommitted changes and commits them in parallel.
	// This is useful for batch committing across multiple repositories at once.
	BulkCommit(ctx context.Context, opts BulkCommitOptions) (*BulkCommitResult, error)

	// BulkDiff scans for repositories and gets their diffs in parallel.
	// This is useful for reviewing changes across multiple repositories or for LLM-based commit message generation.
	BulkDiff(ctx context.Context, opts BulkDiffOptions) (*BulkDiffResult, error)

	// BulkClean scans for repositories and removes untracked/ignored files in parallel.
	// Dry-run by default; set DryRun=false (via --force) to actually delete files.
	BulkClean(ctx context.Context, opts BulkCleanOptions) (*BulkCleanResult, error)

	// BulkCleanup scans for repositories and performs branch cleanup in parallel.
	// This is useful for cleaning up merged, stale, or gone branches across multiple repositories.
	BulkCleanup(ctx context.Context, opts BulkCleanupOptions) (*BulkCleanupResult, error)

	// BulkStash scans for repositories and performs stash operations in parallel.
	// This is useful for stashing/popping changes across multiple repositories.
	BulkStash(ctx context.Context, opts BulkStashOptions) (*BulkStashResult, error)

	// BulkTag scans for repositories and performs tag operations in parallel.
	// This is useful for creating/pushing tags across multiple repositories.
	BulkTag(ctx context.Context, opts BulkTagOptions) (*BulkTagResult, error)

	// BulkClone clones multiple repositories from URLs in parallel.
	// Supports --update to pull existing repositories instead of skipping.
	BulkClone(ctx context.Context, opts BulkCloneOptions) (*BulkCloneResult, error)

	// BulkBranchList scans for repositories and lists their branches in parallel.
	// This is useful for listing branches across multiple repositories at once.
	BulkBranchList(ctx context.Context, opts BulkBranchListOptions) (*BulkBranchListResult, error)

	// BulkExec scans for repositories and runs an arbitrary command in each
	// working tree in parallel. Command is executed without a shell.
	BulkExec(ctx context.Context, opts BulkExecOptions) (*BulkExecResult, error)

	// ScanRepositories scans a directory for Git repositories and returns their paths.
	// This is a lightweight scan-only operation (no GetInfo/GetStatus) used when
	// only repository discovery is needed (e.g., before DiagnosticExecutor).
	ScanRepositories(ctx context.Context, opts ScanOptions) (*ScanResult, error)

	// IsRepository checks if the path points to a valid Git repository.
	// Returns true if the path contains a .git directory or is a bare repository.
	IsRepository(ctx context.Context, path string) bool

	// GetInfo retrieves detailed information about a repository.
	// This includes configuration, remote URLs, and other metadata.
	GetInfo(ctx context.Context, repo *Repository) (*Info, error)

	// GetStatus retrieves the current working tree status.
	// This shows modified, staged, untracked files, etc.
	GetStatus(ctx context.Context, repo *Repository) (*Status, error)

	// IncomingForeignWork lists the commits the upstream branch has that the
	// local branch does not, signed by a device or agent other than mine.
	// It names the other writers on a shared branch; an unnamed identity, or a
	// branch with no upstream, yields nothing.
	IncomingForeignWork(ctx context.Context, repoPath string, mine identity.Identity) ([]ForeignCommit, error)

	// ResolveBase picks the integration branch for a repository (config
	// defaultBranch first, then a heuristic fallback) and reports how HEAD
	// diverges from it. See BaseBranchInfo for the policy and the source label.
	ResolveBase(ctx context.Context, repo *Repository, candidates []string) (BaseBranchInfo, error)

	// MergedBranches lists local branches whose tips are already ancestors of
	// base — work that has landed and whose branch is now reclaimable. Base
	// itself and the current branch are excluded.
	MergedBranches(ctx context.Context, repo *Repository, base string) ([]string, error)

	// BotRemoteBranches partitions origin remote-tracking refs with bot prefixes
	// into merged (tip is an ancestor of base), superseded (not an ancestor,
	// but base already satisfies the bot's version target), and pending (not
	// an ancestor and still newer or not comparable). Names are returned
	// without the remote prefix. Skips origin/HEAD and IsProtected names.
	// Empty base returns nil, nil, nil, nil.
	BotRemoteBranches(ctx context.Context, repo *Repository, base string) (merged, superseded, pending []string, err error)

	// SyncBase fast-forwards a repository's local base ref to its
	// remote-tracking counterpart without checking it out. `git fetch` updates
	// refs/remotes/* and `git pull` updates only the checked-out branch, so a
	// base branch nobody checks out is updated by neither and drifts silently.
	// opts.Candidates is the same ordered list ResolveBase takes, so the branch
	// this repairs is the branch `info` reports on. See BaseSyncResult.
	SyncBase(ctx context.Context, repoPath string, opts BaseSyncOptions) (BaseSyncResult, error)
}

Client defines core repository operations. This is the primary interface for interacting with Git repositories. All methods accept context.Context for cancellation and timeout support.

func NewClient

func NewClient(opts ...ClientOption) Client

NewClient creates a new repository client with the given options. The client provides access to all repository operations defined in the Client interface.

Example:

client := repository.NewClient(
    repository.WithLogger(myLogger),
    repository.WithTimeout(30 * time.Second),
)

type ClientOption

type ClientOption func(*client)

ClientOption configures a Client.

func WithClientLogger

func WithClientLogger(logger Logger) ClientOption

WithClientLogger sets a custom logger for the client.

func WithExecutor

func WithExecutor(executor *gitcmd.Executor) ClientOption

WithExecutor sets a custom Git executor for the client. This is primarily useful for testing with a mock executor.

type CloneOption

type CloneOption func(*CloneOptions)

CloneOption is a functional option for configuring clone operations.

func WithBranch

func WithBranch(branch string) CloneOption

WithBranch sets the branch to check out after cloning.

func WithDepth

func WithDepth(depth int) CloneOption

WithDepth sets the clone depth (for shallow clones). A depth of 1 creates a shallow clone with only the latest commit.

func WithLogger

func WithLogger(logger Logger) CloneOption

WithLogger sets a logger for the clone operation.

func WithProgress

func WithProgress(progress ProgressReporter) CloneOption

WithProgress sets a progress reporter for the clone operation.

func WithRecursive

func WithRecursive() CloneOption

WithRecursive enables recursive submodule cloning.

func WithSingleBranch

func WithSingleBranch() CloneOption

WithSingleBranch enables single-branch mode (only clone specified branch).

type CloneOptions

type CloneOptions struct {
	// URL is the repository URL to clone (required).
	URL string

	// Destination is the local path where the repository will be cloned (required).
	Destination string

	// Branch is the branch to check out after cloning.
	// If empty, the remote's default branch is used.
	Branch string

	// Depth limits the clone depth (number of commits).
	// 0 means full clone, 1 means shallow clone with only the latest commit.
	Depth int

	// SingleBranch clones only the specified branch.
	// If true, other branches are not fetched.
	SingleBranch bool

	// Recursive clones submodules recursively.
	Recursive bool

	// Bare creates a bare repository (no working directory).
	Bare bool

	// Mirror creates a mirror repository (all refs are copied).
	Mirror bool

	// Quiet suppresses progress output.
	Quiet bool

	// CreateBranch creates the branch if it doesn't exist on the remote.
	// If true and the specified branch doesn't exist, it will be created after cloning.
	// Only effective when Branch is specified.
	CreateBranch bool

	// Progress is an optional progress reporter.
	// If provided, clone progress will be reported.
	Progress ProgressReporter

	// Logger is an optional logger.
	// If provided, clone operations will be logged.
	Logger Logger

	// Env contains additional environment variables for the git command.
	// Used for authentication (e.g., GIT_SSH_COMMAND for SSH keys).
	Env []string
}

CloneOptions configures repository cloning. Use the With* functions to set options (functional options pattern).

type CloneOrUpdateOptions

type CloneOrUpdateOptions struct {
	// URL is the repository URL to clone (required)
	URL string

	// Destination is the local path where the repository will be cloned (required)
	Destination string

	// Strategy defines how to handle an existing repository
	// Default: StrategyRebase
	Strategy UpdateStrategy

	// Branch is the branch to check out after cloning
	// If empty, the remote's default branch is used
	Branch string

	// ExactBranchPrepared records that the caller has successfully fetched the
	// exact Branch ref from origin immediately before this reset. It is only
	// meaningful with StrategyReset and a non-empty Branch; when true, reset
	// uses that prepared remote-tracking ref without another network fetch.
	// Direct callers should leave it false, which preserves the normal fetch.
	ExactBranchPrepared bool

	// Depth limits the clone depth (number of commits)
	// 0 means full clone, 1 means shallow clone with only the latest commit
	Depth int

	// Force allows destructive operations even when not normally allowed
	Force bool

	// CreateBranch creates the branch if it doesn't exist on the remote
	// If true and the specified branch doesn't exist, it will be created after cloning
	// Only effective when Branch is specified
	CreateBranch bool

	// SingleBranch clones only the history of the target branch (--single-branch)
	SingleBranch bool

	// Recursive initializes and clones submodules (--recurse-submodules)
	Recursive bool

	// Logger is an optional logger for operation feedback
	Logger Logger

	// Progress is an optional progress reporter
	Progress ProgressReporter

	// Env contains additional environment variables for the git command.
	// Used for authentication (e.g., GIT_SSH_COMMAND for SSH keys).
	Env []string
}

CloneOrUpdateOptions configures the clone-or-update operation.

type CloneOrUpdateResult

type CloneOrUpdateResult struct {
	// Repository is the opened repository (nil if skipped)
	Repository *Repository

	// Action describes what action was taken
	Action string // "cloned", "updated", "skipped", "reset", etc.

	// StrategyUsed is the strategy that was actually used
	StrategyUsed UpdateStrategy

	// Success indicates if the operation succeeded
	Success bool

	// Message contains a human-readable result message
	Message string
}

CloneOrUpdateResult contains the result of a clone-or-update operation.

type Config

type Config struct {
	// User is the user configuration.
	User UserConfig

	// Core is the core Git configuration.
	Core CoreConfig

	// Remote contains remote configurations.
	Remote map[string]Remote

	// Branch contains branch configurations.
	Branch map[string]BranchConfig
}

Config represents repository configuration.

type CoreConfig

type CoreConfig struct {
	// RepositoryFormatVersion is the repository format version.
	RepositoryFormatVersion int

	// FileMode indicates if file mode is tracked.
	FileMode bool

	// Bare indicates if this is a bare repository.
	Bare bool

	// LogAllRefUpdates indicates if all ref updates are logged.
	LogAllRefUpdates bool

	// IgnoreCase indicates if file names are case-insensitive.
	IgnoreCase bool

	// PrecomposeUnicode indicates if Unicode is precomposed.
	PrecomposeUnicode bool
}

CoreConfig represents core Git configuration.

type DirectoryStructure

type DirectoryStructure string

DirectoryStructure defines how clone destinations are organized.

const (
	// StructureFlat clones all repos directly into target directory.
	// Example: github.com/user/repo → ./repo/.
	StructureFlat DirectoryStructure = "flat"

	// StructureUser organizes repos by user/org name.
	// Example: github.com/user/repo → ./user/repo/.
	StructureUser DirectoryStructure = "user"
)

type FileStatus

type FileStatus string

FileStatus represents the status of a file in the working tree.

const (
	// FileStatusUnmodified indicates the file is unchanged.
	FileStatusUnmodified FileStatus = " "

	// FileStatusModified indicates the file has been modified.
	FileStatusModified FileStatus = "M"

	// FileStatusAdded indicates the file has been added.
	FileStatusAdded FileStatus = "A"

	// FileStatusDeleted indicates the file has been deleted.
	FileStatusDeleted FileStatus = "D"

	// FileStatusRenamed indicates the file has been renamed.
	FileStatusRenamed FileStatus = "R"

	// FileStatusCopied indicates the file has been copied.
	FileStatusCopied FileStatus = "C"

	// FileStatusUntracked indicates the file is untracked.
	FileStatusUntracked FileStatus = "?"

	// FileStatusIgnored indicates the file is ignored.
	FileStatusIgnored FileStatus = "!"

	// FileStatusConflict indicates the file has a merge conflict.
	FileStatusConflict FileStatus = "U"
)

func (FileStatus) String

func (f FileStatus) String() string

String returns the string representation of the file status.

type Finding

type Finding struct {
	Code     string         `json:"code"`
	Severity string         `json:"severity"`
	Message  string         `json:"message"`
	Evidence map[string]any `json:"evidence,omitempty"`
	Fix      *Remediation   `json:"fix,omitempty"`
}

Finding is one actionable observation.

type ForceMode

type ForceMode string

ForceMode decides which kind of force push a policy tolerates.

const (
	// ForceModeLeaseOnly permits --force, which pushes with --force-with-lease,
	// and refuses a "+" refspec, which does not. It is the default.
	ForceModeLeaseOnly ForceMode = "lease-only"

	// ForceModeAllow permits every force push, including an unleased one.
	ForceModeAllow ForceMode = "allow"

	// ForceModeDeny refuses every force push.
	ForceModeDeny ForceMode = "deny"
)

func ValidateForceMode

func ValidateForceMode(value string) (ForceMode, error)

ValidateForceMode resolves a configured or flag-supplied force mode. An empty value is the default rather than an error, so callers can pass through an unset flag.

type ForeignCommit

type ForeignCommit struct {
	Hash     string            `json:"hash"`
	Subject  string            `json:"subject"`
	Identity identity.Identity `json:"identity"`
}

ForeignCommit is a commit on the remote branch that a force push would throw away, signed by a machine or agent other than this one.

func (ForeignCommit) String

func (c ForeignCommit) String() string

String renders the commit for an error message.

type ForeignWorkMode

type ForeignWorkMode string

ForeignWorkMode decides what happens to a force push that would discard commits another machine or agent made.

const (
	// ForeignWorkBlock refuses the push. It is the default.
	ForeignWorkBlock ForeignWorkMode = "block"
	// ForeignWorkAllow permits it, leaving --force-with-lease as the only guard.
	ForeignWorkAllow ForeignWorkMode = "allow"
)

ForeignWorkMode values.

func ValidateForeignWorkMode

func ValidateForeignWorkMode(value string) (ForeignWorkMode, error)

ValidateForeignWorkMode resolves a configured or flag-supplied mode. An empty value is the default rather than an error, so callers can pass an unset flag straight through.

type Info

type Info struct {
	// Branch is the current branch name (e.g., "main", "master").
	// Empty if in detached HEAD state.
	Branch string

	// Commit is the current HEAD commit hash (full SHA-1).
	Commit string

	// Remote is the default remote name (usually "origin").
	Remote string

	// RemoteURL is the URL of the default remote.
	RemoteURL string

	// IsDirty indicates if there are uncommitted changes.
	IsDirty bool

	// Upstream is the upstream branch (e.g., "origin/main").
	// Empty if no upstream is configured.
	Upstream string

	// AheadBy is the number of commits ahead of upstream.
	AheadBy int

	// BehindBy is the number of commits behind upstream.
	BehindBy int

	// Remotes contains all configured remotes (name -> url).
	Remotes map[string]string

	// HeadSHA is the short hash of the current commit.
	HeadSHA string

	// Describe is the result of 'git describe' (e.g. v1.0.0-5-g123abc).
	Describe string

	// LastCommitMsg is the subject of the last commit.
	LastCommitMsg string

	// LastCommitDate is the relative date of the last commit (e.g. "2 hours ago").
	LastCommitDate string

	// LastCommitAuthor is the author of the last commit.
	LastCommitAuthor string

	// LocalBranches contains the list of local branch names.
	LocalBranches []string

	// RemoteBranches contains remote-tracking branch names in remote/branch
	// form. Symbolic remote HEAD refs are excluded.
	RemoteBranches []string

	// StashCount is the number of stash entries.
	StashCount int

	// OldestStash is when the oldest stash entry was created, zero when there
	// is none. A stash is invisible to every other machine, so its age is the
	// difference between work in progress and work that was forgotten.
	OldestStash time.Time
}

Info contains detailed repository information.

type Logger

type Logger interface {
	// Debug logs a debug-level message with optional key-value pairs.
	Debug(msg string, args ...any)

	// Info logs an info-level message with optional key-value pairs.
	Info(msg string, args ...any)

	// Warn logs a warning-level message with optional key-value pairs.
	Warn(msg string, args ...any)

	// Error logs an error-level message with optional key-value pairs.
	Error(msg string, args ...any)
}

Logger provides a logging interface for library consumers. This allows the library to integrate with any logging framework without taking a hard dependency on a specific logger.

Library code should accept Logger via dependency injection. CLI code can provide a concrete logger implementation.

func NewNoopLogger

func NewNoopLogger() Logger

NewNoopLogger creates a no-op logger. This is useful for testing or when logging is not needed.

func NewWriterLogger

func NewWriterLogger(w io.Writer) Logger

NewWriterLogger creates a logger that writes to an io.Writer. This is useful for simple logging to stdout/stderr.

type OmittedFile

type OmittedFile struct {
	// Path is the repository-relative file path
	Path string

	// Reason is why the content was omitted: "not-regular-file", "too-large",
	// or "read-error"
	Reason string
}

OmittedFile records an untracked file that was left out of the diff body.

type OperationType

type OperationType string

OperationType represents the type of Git operation being performed.

const (
	// OperationClone represents a clone operation.
	OperationClone OperationType = "clone"

	// OperationPull represents a pull operation.
	OperationPull OperationType = "pull"

	// OperationFetch represents a fetch operation.
	OperationFetch OperationType = "fetch"

	// OperationPush represents a push operation.
	OperationPush OperationType = "push"

	// OperationReset represents a reset operation.
	OperationReset OperationType = "reset"

	// OperationStatus represents a status operation.
	OperationStatus OperationType = "status"
)

func (OperationType) String

func (o OperationType) String() string

String returns the string representation of the operation type.

type ParsedRefspec

type ParsedRefspec struct {
	// Source is the local branch/ref (left side of :)
	Source string
	// Destination is the remote branch/ref (right side of :), empty if not specified
	Destination string
	// Force indicates if this is a force push (+ prefix)
	Force bool
}

ParsedRefspec represents a parsed Git refspec.

func ValidateRefspec

func ValidateRefspec(refspec string) (*ParsedRefspec, error)

ValidateRefspec validates a Git refspec string and returns parsed components. Returns an error if the refspec is invalid.

Valid formats:

  • "branch" -> push local branch to remote branch with same name
  • "local:remote" -> push local branch to remote branch
  • "+local:remote" -> force push local to remote
  • "refs/heads/main:refs/heads/master" -> full ref path

Invalid formats:

  • "" -> empty refspec
  • "local::remote" -> double colon
  • "local:remote:extra" -> too many colons
  • "-invalid" -> branch name starting with -
  • "branch." -> branch name ending with .
  • "branch..name" -> consecutive dots
  • "branch name" -> contains space

func (*ParsedRefspec) GetDestinationBranch

func (r *ParsedRefspec) GetDestinationBranch() string

GetDestinationBranch returns the destination branch name. If destination is empty, returns source branch name. If the destination is a full ref path (refs/heads/branch), it extracts the branch name.

func (*ParsedRefspec) GetSourceBranch

func (r *ParsedRefspec) GetSourceBranch() string

GetSourceBranch returns the source branch name. If the source is a full ref path (refs/heads/branch), it extracts the branch name.

func (*ParsedRefspec) String

func (r *ParsedRefspec) String() string

String returns the string representation of a refspec.

type ProgressReporter

type ProgressReporter interface {
	// Start initializes the progress reporter with the total amount of work.
	// The total may be in bytes, number of files, or other units depending on the operation.
	Start(total int64)

	// Update reports current progress.
	// The current value should be <= total.
	Update(current int64)

	// Done signals that the operation is complete.
	// This should be called even if the operation fails.
	Done()
}

ProgressReporter provides progress feedback for long-running operations. This allows library consumers to display progress bars or other UI feedback.

func NewNoopProgress

func NewNoopProgress() ProgressReporter

NewNoopProgress creates a no-op progress reporter. This is useful for testing or when progress reporting is not needed.

type PushDenial

type PushDenial struct {
	Rule   PushRule
	Branch string
	Detail string
}

PushDenial is a policy's refusal of one repository's push.

type PushIntent

type PushIntent struct {
	// Branch is the branch HEAD is on, used when no refspec names a target.
	Branch string
	// Refspec is the --refspec value, if one was given.
	Refspec string
	// Force reports whether --force was given, which pushes with a lease.
	Force bool
}

PushIntent describes what a single repository is about to push.

type PushPolicy

type PushPolicy struct {
	// Protected lists branch names and trailing-* patterns that may not be
	// pushed to at all, matching the pattern syntax used for deletion.
	Protected []string `yaml:"protected,omitempty"`

	// ForceMode decides which force pushes are allowed to every other branch.
	ForceMode ForceMode `yaml:"forceMode,omitempty"`

	// ForeignWork decides what happens to a force push that would discard
	// commits signed by another machine or agent. Unset means block.
	//
	// Unlike the rules above it cannot be decided from intent alone: it needs
	// to read the commits the remote has and this machine does not.
	ForeignWork ForeignWorkMode `yaml:"foreignWork,omitempty"`
}

PushPolicy restricts which branches a push may write and how.

It is separate from ProtectedBranches, which guards deletion with a built-in list. This one starts empty: refusing to push to main is a workflow decision a project makes, not something the tool assumes.

func (*PushPolicy) Check

func (p *PushPolicy) Check(intent PushIntent) *PushDenial

Check reports why the policy refuses this push, or nil if it permits it. A nil policy permits everything.

type PushRule

type PushRule string

PushRule names the rule a push ran into.

const (
	// PushRuleProtected marks a push to a branch listed as protected.
	PushRuleProtected PushRule = "protected-branch"
	// PushRuleRawForce marks a "+" refspec under lease-only mode.
	PushRuleRawForce PushRule = "raw-force"
	// PushRuleForceDenied marks any force push under deny mode.
	PushRuleForceDenied PushRule = "force-denied"
	// PushRuleForeignWork marks a force push that would discard commits
	// signed by another machine or agent.
	PushRuleForeignWork PushRule = "foreign-work"
)

type Ref

type Ref struct {
	// Name is the reference name (e.g., "refs/heads/main", "main").
	Name string

	// Hash is the commit hash this reference points to.
	Hash string

	// Type is the reference type (branch, tag, remote).
	Type RefType
}

Ref represents a Git reference (branch, tag, or commit).

type RefType

type RefType string

RefType represents the type of Git reference.

const (
	// RefTypeBranch represents a local branch reference.
	RefTypeBranch RefType = "branch"

	// RefTypeRemoteBranch represents a remote branch reference.
	RefTypeRemoteBranch RefType = "remote-branch"

	// RefTypeTag represents a tag reference.
	RefTypeTag RefType = "tag"

	// RefTypeCommit represents a direct commit reference.
	RefTypeCommit RefType = "commit"
)

func (RefType) String

func (r RefType) String() string

String returns the string representation of the reference type.

type Remediation

type Remediation struct {
	Action     string   `json:"action"`
	Command    []string `json:"command,omitempty"`
	Autofix    bool     `json:"autofix"`
	Reversible bool     `json:"reversible"`
	Note       string   `json:"note,omitempty"`
}

Remediation is the typed repair contract.

Command is argv, never a shell string: the caller execs it directly, so a branch name containing shell metacharacters cannot become a second command. Autofix is a policy answer ("is an agent allowed to run this unattended"), deliberately separate from Reversible, which is a fact about the operation. Keeping them apart lets policy tighten without rewriting the catalog.

type Remote

type Remote struct {
	// Name is the remote name (e.g., "origin").
	Name string

	// URL is the remote URL.
	URL string

	// Type is the remote type (https, ssh, git, file).
	Type RemoteType

	// FetchRefs are the fetch refspecs.
	FetchRefs []string

	// PushRefs are the push refspecs.
	PushRefs []string
}

Remote represents a Git remote configuration.

type RemoteType

type RemoteType string

RemoteType represents the type of Git remote.

const (
	// RemoteTypeHTTPS represents an HTTPS remote.
	RemoteTypeHTTPS RemoteType = "https"

	// RemoteTypeSSH represents an SSH remote.
	RemoteTypeSSH RemoteType = "ssh"

	// RemoteTypeGit represents a git:// protocol remote.
	RemoteTypeGit RemoteType = "git"

	// RemoteTypeFile represents a local file path remote.
	RemoteTypeFile RemoteType = "file"
)

func (RemoteType) String

func (r RemoteType) String() string

String returns the string representation of the remote type.

type RenamedFile

type RenamedFile struct {
	// OldPath is the original file path.
	OldPath string

	// NewPath is the new file path.
	NewPath string
}

RenamedFile represents a file that has been renamed.

type Repository

type Repository struct {
	// Path is the absolute path to the repository root.
	// This is the directory containing the .git directory (or the repository itself if bare).
	Path string

	// GitDir is the path to the .git directory.
	// For normal repositories, this is Path/.git
	// For bare repositories, this is the same as Path
	// For worktrees, this points to the linked .git file
	GitDir string

	// WorkTree is the working tree path.
	// For normal repositories, this is the same as Path
	// For bare repositories, this is empty
	// For worktrees, this may differ from Path
	WorkTree string

	// IsBare indicates if this is a bare repository (no working tree).
	IsBare bool

	// IsShallow indicates if this is a shallow clone (partial history).
	IsShallow bool
}

Repository represents a Git repository handle. This is returned by Open and Clone operations and passed to other methods.

type RepositoryBranchListResult

type RepositoryBranchListResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// CurrentBranch is the currently checked out branch
	CurrentBranch string

	// Branches is the list of branches
	Branches []BranchInfo

	// LocalCount is the number of local branches
	LocalCount int

	// RemoteCount is the number of remote branches
	RemoteCount int
}

RepositoryBranchListResult represents branch list for a single repository.

func (RepositoryBranchListResult) GetStatus

func (r RepositoryBranchListResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryCleanResult

type RepositoryCleanResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// FilesRemoved is the list of files removed or would be removed
	FilesRemoved []string

	// FilesCount is the number of files removed or would be removed
	FilesCount int
}

RepositoryCleanResult represents the result for a single repository clean.

func (RepositoryCleanResult) GetStatus

func (r RepositoryCleanResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryCleanupResult

type RepositoryCleanupResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// MergedCount is the number of merged branches found/deleted
	MergedCount int

	// StaleCount is the number of stale branches found/deleted
	StaleCount int

	// GoneCount is the number of gone branches found/deleted
	GoneCount int

	// SupersededCount is the number of superseded bot remotes found/deleted
	SupersededCount int

	// NonCanonicalCount is the number of branches retired for duplicating the
	// declared canonical branch
	NonCanonicalCount int

	// ProtectedCount is the number of protected branches skipped
	ProtectedCount int

	// TotalAnalyzed is the total number of branches analyzed
	TotalAnalyzed int

	// DeletedBranches is the list of deleted branch names
	DeletedBranches []string

	// Branches is the per-branch account (name, reason, location, kind)
	// used by machine-readable printers. DeletedBranches stays a flat name
	// list for existing human output.
	Branches []CleanupBranchEntry

	// FailedBranches records candidates the run tried to delete and could not.
	// A candidate count is not a deletion count: git refuses some deletes
	// (a remote's default branch, most commonly), and reporting those as
	// deleted would tell the operator the tree is clean when it is not.
	FailedBranches []CleanupFailureEntry

	// RetireRefusals records trunk-named branches the non-canonical gate
	// examined and declined. They were never candidates, so they are kept apart
	// from FailedBranches: folding them in would inflate TotalBranchesFailed and
	// turn a repository that is behaving correctly into a failing one.
	RetireRefusals []RetireRefusalEntry
}

RepositoryCleanupResult represents the result for a single repository cleanup.

func (RepositoryCleanupResult) GetStatus

func (r RepositoryCleanupResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryCloneResult

type RepositoryCloneResult struct {
	// URL is the repository URL.
	URL string

	// Path is the local path where it was cloned.
	Path string

	// RelativePath is the path relative to the base directory.
	RelativePath string

	// Status is the operation status.
	Status string // "cloned", "updated", "skipped", "error", "would-clone", "would-update"

	// Branch is the checked out branch.
	Branch string

	// Duration is how long the operation took.
	Duration time.Duration

	// Error is set if the operation failed.
	Error error
}

RepositoryCloneResult represents the result for a single repository.

func (RepositoryCloneResult) GetStatus

func (r RepositoryCloneResult) GetStatus() string

GetStatus implements statusGetter for generic summary calculation.

type RepositoryCommitResult

type RepositoryCommitResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Branch is the current branch
	Branch string

	// Status is the operation status (success, skipped, error, would-commit)
	Status string

	// CommitHash is the commit hash if successful
	CommitHash string

	// Message is the commit message used
	Message string

	// SuggestedMessage is the auto-generated message (for preview)
	SuggestedMessage string

	// FilesChanged is the number of files changed
	FilesChanged int

	// TrackedFilesChanged, UntrackedFilesChanged and StagedFilesChanged break
	// FilesChanged down. The untracked count is the one that used to be wrong:
	// without -uall, `?? docs/` counted as a single file while the commit
	// recorded every file beneath it.
	TrackedFilesChanged   int
	UntrackedFilesChanged int
	StagedFilesChanged    int

	// Additions is the number of lines added
	Additions int

	// Deletions is the number of lines deleted
	Deletions int

	// ChangedFiles is the list of changed files
	ChangedFiles []string

	// ConflictedFiles is the list of unmerged files. Non-empty means the
	// repository was not committed (unless AllowConflicted was set).
	ConflictedFiles []string

	// Error if the operation failed
	Error error

	// Duration is the operation time for this repository
	Duration time.Duration
}

RepositoryCommitResult contains the result for a single repository commit.

func (RepositoryCommitResult) GetStatus

func (r RepositoryCommitResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryDiffResult

type RepositoryDiffResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Branch is the current branch
	Branch string

	// Status is the operation status (has-changes, clean, error)
	Status string

	// DiffContent is the actual diff output
	DiffContent string

	// DiffSummary is a short summary of changes
	DiffSummary string

	// FilesChanged is the number of tracked files that differ from the scope's
	// base. It equals TrackedFilesChanged and deliberately excludes untracked
	// files, preserving the meaning this key has always had for diff. The full
	// set a commit would record is TrackedFilesChanged + UntrackedFilesChanged.
	FilesChanged int

	// TrackedFilesChanged, UntrackedFilesChanged and StagedFilesChanged name the
	// three counts separately, so a caller can tell why diff and commit report
	// the numbers they do instead of having to guess which set each one means.
	// StagedFilesChanged overlaps the other two rather than partitioning them.
	TrackedFilesChanged   int
	UntrackedFilesChanged int
	StagedFilesChanged    int

	// Scope names the comparison these numbers describe ("head", "staged" or
	// "worktree"). See ChangeScope.
	Scope string

	// Additions is the number of lines added
	Additions int

	// Deletions is the number of lines deleted
	Deletions int

	// ChangedFiles is the list of changed files with their status
	ChangedFiles []ChangedFile

	// UntrackedFiles is the list of untracked files
	UntrackedFiles []string

	// OmittedFiles lists untracked files whose content was left out of
	// DiffContent, with the reason. Never silently empty: every skip in the
	// untracked reader is recorded here.
	OmittedFiles []OmittedFile

	// Truncated indicates if the diff was truncated due to size limits
	Truncated bool

	// Error if the operation failed
	Error error

	// Duration is the operation time for this repository
	Duration time.Duration
}

RepositoryDiffResult contains the diff result for a single repository.

func (RepositoryDiffResult) GetStatus

func (r RepositoryDiffResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryExecResult

type RepositoryExecResult struct {
	Path         string
	RelativePath string
	Status       string
	Message      string
	Error        error
	Duration     time.Duration
	ExitCode     int
	Output       string
}

RepositoryExecResult is the per-repository exec outcome.

func (RepositoryExecResult) GetStatus

func (r RepositoryExecResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryFetchResult

type RepositoryFetchResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (success, skipped, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// FetchedRefs is the number of refs fetched
	FetchedRefs int

	// FetchedObjects is the number of objects fetched
	FetchedObjects int

	// CommitsBehind is the number of commits behind remote after fetch
	CommitsBehind int

	// CommitsAhead is the number of commits ahead of remote after fetch
	CommitsAhead int

	// Deprecated: use TrackedChangedFiles. The name promised "uncommitted files"
	// but the value was len(StagedFiles)+len(ModifiedFiles), which counted a path
	// that is both staged and modified twice and missed working-tree-only
	// deletions entirely. It now carries the TrackedChangedFiles value.
	UncommittedFiles int

	// TrackedChangedFiles is the number of distinct tracked paths with
	// uncommitted changes, staged or not - checked after fetch
	TrackedChangedFiles int

	// StagedFiles is the number of paths whose index differs from HEAD - checked after fetch
	StagedFiles int

	// UnstagedFiles is the number of tracked paths whose working tree differs
	// from the index - checked after fetch
	UnstagedFiles int

	// UntrackedFiles is the number of untracked files - checked after fetch
	UntrackedFiles int
}

RepositoryFetchResult represents the result for a single repository fetch.

func (RepositoryFetchResult) GetError

func (r RepositoryFetchResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositoryFetchResult) GetMessage

func (r RepositoryFetchResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositoryFetchResult) GetPath

func (r RepositoryFetchResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositoryFetchResult) GetStatus

func (r RepositoryFetchResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryPullResult

type RepositoryPullResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (success, skipped, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// CommitsBehind is the number of commits behind remote before pull
	CommitsBehind int

	// CommitsAhead is the number of commits ahead of remote before pull
	CommitsAhead int

	// UpdatedFiles is the number of files changed
	UpdatedFiles int

	// Stashed indicates if local changes were stashed
	Stashed bool

	// Deprecated: use TrackedChangedFiles. See RepositoryFetchResult for why the
	// old value was wrong; it now carries the TrackedChangedFiles value.
	UncommittedFiles int

	// TrackedChangedFiles is the number of distinct tracked paths with
	// uncommitted changes, staged or not - checked after pull
	TrackedChangedFiles int

	// StagedFiles is the number of paths whose index differs from HEAD - checked after pull
	StagedFiles int

	// UnstagedFiles is the number of tracked paths whose working tree differs
	// from the index - checked after pull
	UnstagedFiles int

	// UntrackedFiles is the number of untracked files - checked after pull
	UntrackedFiles int
}

RepositoryPullResult represents the result for a single repository pull.

func (RepositoryPullResult) GetError

func (r RepositoryPullResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositoryPullResult) GetMessage

func (r RepositoryPullResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositoryPullResult) GetPath

func (r RepositoryPullResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositoryPullResult) GetStatus

func (r RepositoryPullResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryPushResult

type RepositoryPushResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (success, skipped, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// CommitsAhead is the number of commits ahead of remote before push
	CommitsAhead int

	// PushedCommits is the number of commits pushed
	PushedCommits int

	// Deprecated: use TrackedChangedFiles. See RepositoryFetchResult for why the
	// old value was wrong; it now carries the TrackedChangedFiles value.
	UncommittedFiles int

	// TrackedChangedFiles is the number of distinct tracked paths with
	// uncommitted changes, staged or not - checked after push
	TrackedChangedFiles int

	// StagedFiles is the number of paths whose index differs from HEAD - checked after push
	StagedFiles int

	// UnstagedFiles is the number of tracked paths whose working tree differs
	// from the index - checked after push
	UnstagedFiles int

	// UntrackedFiles is the number of untracked files - checked after push
	UntrackedFiles int
}

RepositoryPushResult represents the result for a single repository push.

func (RepositoryPushResult) GetError

func (r RepositoryPushResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositoryPushResult) GetMessage

func (r RepositoryPushResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositoryPushResult) GetPath

func (r RepositoryPushResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositoryPushResult) GetStatus

func (r RepositoryPushResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryStashResult

type RepositoryStashResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// StashCount is the number of stashes in the repository
	StashCount int

	// StashMessage is the stash message (for save operation)
	StashMessage string
}

RepositoryStashResult represents the result for a single repository stash operation.

func (RepositoryStashResult) GetStatus

func (r RepositoryStashResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryStatusResult

type RepositoryStatusResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (clean, dirty, conflict, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// Upstream is the tracking ref for the current branch (e.g.
	// "origin/master"), empty when the branch tracks nothing. CommitsAhead and
	// CommitsBehind are measured against it, so both are zero when it is empty
	// — without this field a caller cannot tell "in sync" from "nothing to
	// compare against", which are opposite situations.
	Upstream string

	// Remotes contains all configured remotes (name -> url).
	Remotes map[string]string

	// Metadata
	HeadSHA          string
	Describe         string
	LastCommitMsg    string
	LastCommitDate   string
	LastCommitAuthor string
	LocalBranches    []string
	RemoteBranches   []string
	StashCount       int

	// OldestStash is when the oldest stash entry was created, zero when there
	// is none. A stash never leaves this machine, so its age separates work in
	// progress from work that was forgotten here.
	OldestStash time.Time

	// CommitsBehind is how many commits behind remote
	CommitsBehind int

	// CommitsAhead is how many commits ahead of remote
	CommitsAhead int

	// Deprecated: use TrackedChangedFiles. Unlike the other three results, this
	// one was assigned the raw porcelain line count, so it also included every
	// untracked entry — which UntrackedFiles below then reported a second time.
	// It now carries the TrackedChangedFiles value and untracked paths appear
	// once, in UntrackedFiles.
	UncommittedFiles int

	// TrackedChangedFiles is the number of distinct tracked paths with
	// uncommitted changes, staged or not
	TrackedChangedFiles int

	// StagedFiles is the number of paths whose index differs from HEAD
	StagedFiles int

	// UnstagedFiles is the number of tracked paths whose working tree differs
	// from the index
	UnstagedFiles int

	// UntrackedFiles is the number of untracked files
	UntrackedFiles int

	// ConflictFiles is the list of files with conflicts
	ConflictFiles []string

	// RebaseInProgress indicates if repository is in rebase state
	RebaseInProgress bool

	// MergeInProgress indicates if repository is in merge state
	MergeInProgress bool
}

RepositoryStatusResult represents the status result for a single repository.

func (RepositoryStatusResult) GetError

func (r RepositoryStatusResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositoryStatusResult) GetMessage

func (r RepositoryStatusResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositoryStatusResult) GetPath

func (r RepositoryStatusResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositoryStatusResult) GetStatus

func (r RepositoryStatusResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositorySwitchResult

type RepositorySwitchResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (switched, already-on-branch, dirty, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// PreviousBranch is the branch before switching
	PreviousBranch string

	// CurrentBranch is the branch after switching (or current if not switched)
	CurrentBranch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// HasUncommittedChanges indicates if there were local changes preventing switch
	HasUncommittedChanges bool
}

RepositorySwitchResult represents the result for a single repository switch.

func (RepositorySwitchResult) GetError

func (r RepositorySwitchResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositorySwitchResult) GetMessage

func (r RepositorySwitchResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositorySwitchResult) GetPath

func (r RepositorySwitchResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositorySwitchResult) GetStatus

func (r RepositorySwitchResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryTagResult

type RepositoryTagResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// TagCount is the number of tags in the repository
	TagCount int

	// LatestTag is the latest tag name
	LatestTag string

	// CreatedTag is the tag that was created
	CreatedTag string
}

RepositoryTagResult represents the result for a single repository tag operation.

func (RepositoryTagResult) GetStatus

func (r RepositoryTagResult) GetStatus() string

GetStatus returns the status for summary calculation.

type RepositoryUpdateResult

type RepositoryUpdateResult struct {
	// Path is the repository path
	Path string

	// RelativePath is the path relative to scan root
	RelativePath string

	// Status is the operation status (success, skipped, error, etc.)
	Status string

	// Message is a human-readable status message
	Message string

	// Error if the operation failed
	Error error

	// Duration is how long this repository took to process
	Duration time.Duration

	// Branch is the current branch name
	Branch string

	// RemoteURL is the remote origin URL
	RemoteURL string

	// Remote is the remote name (e.g., "origin")
	Remote string

	// CommitsBehind is how many commits behind remote
	CommitsBehind int

	// CommitsAhead is how many commits ahead of remote
	CommitsAhead int

	// HasStash indicates if there are stashed changes
	HasStash bool

	// InMergeState indicates if repository is in merge state
	InMergeState bool

	// HasUncommittedChanges indicates if there are local changes
	HasUncommittedChanges bool

	// BaseSync reports what happened to the local base ref, or nil when
	// BulkUpdateOptions.SyncBase was off. Nil and "nothing to do" are different
	// answers and the renderer needs to tell them apart.
	BaseSync *BaseSyncResult
}

RepositoryUpdateResult represents the result for a single repository.

func (RepositoryUpdateResult) GetError

func (r RepositoryUpdateResult) GetError() error

GetError returns the per-repo error, if any.

func (RepositoryUpdateResult) GetMessage

func (r RepositoryUpdateResult) GetMessage() string

GetMessage returns the human-readable status message.

func (RepositoryUpdateResult) GetPath

func (r RepositoryUpdateResult) GetPath() string

GetPath returns RelativePath when set, otherwise Path.

func (RepositoryUpdateResult) GetStatus

func (r RepositoryUpdateResult) GetStatus() string

GetStatus returns the status for summary calculation.

type Result

type Result struct {
	// Success indicates if the operation succeeded.
	Success bool

	// Output contains the operation's stdout output.
	Output string

	// Error contains the operation's stderr output or error message.
	Error string

	// ExitCode is the Git command's exit code.
	// 0 indicates success, non-zero indicates an error.
	ExitCode int

	// Duration is how long the operation took.
	Duration time.Duration

	// Timestamp is when the operation completed.
	Timestamp time.Time
}

Result represents the result of a Git operation.

type RetireRefusalEntry

type RetireRefusalEntry struct {
	Name     string `json:"name"`
	Location string `json:"location"`
	Reason   string `json:"reason"`
}

RetireRefusalEntry is one trunk-named branch the non-canonical gate declined, with the reason in the operator's terms.

It exists so that "there was nothing to clean up here" and "there was something, it was checked, and it is not safe to retire" stop arriving as the same silence. The operator's next move differs between the two, and a tool that refuses without saying why sends them to `git branch -D` instead.

type ScanOptions

type ScanOptions struct {
	// Directory is the root directory to scan for repositories
	Directory string

	// MaxDepth is the maximum directory depth to scan (default: 1)
	MaxDepth int

	// IncludeSubmodules includes git submodules in the scan (default: false)
	IncludeSubmodules bool

	// IncludePattern is a regex pattern for repositories to include
	IncludePattern string

	// ExcludePattern is a regex pattern for repositories to exclude
	ExcludePattern string

	// Logger for operation feedback
	Logger Logger
}

ScanOptions configures repository scanning without processing.

type ScanResult

type ScanResult struct {
	// Paths contains the absolute paths of discovered repositories.
	Paths []string

	// TotalScanned is the number of repositories found before filtering.
	TotalScanned int

	// Directory is the resolved absolute root directory.
	Directory string
}

ScanResult contains the results of a repository scan.

type Status

type Status struct {
	// IsClean is true if there are no changes (working tree matches HEAD).
	IsClean bool

	// ModifiedFiles are files with unstaged changes.
	ModifiedFiles []string

	// StagedFiles are files staged for commit.
	StagedFiles []string

	// UntrackedFiles are files not tracked by Git.
	UntrackedFiles []string

	// ConflictFiles are files with merge conflicts.
	ConflictFiles []string

	// DeletedFiles are files deleted but not staged.
	DeletedFiles []string

	// RenamedFiles are files that have been renamed.
	RenamedFiles []RenamedFile

	// StagedCount is the number of paths whose index differs from HEAD.
	//
	// The three counts below cannot be recovered from the slices above, which is
	// why they are stored rather than derived: porcelain reports a two-character
	// XY code per path, and the slices keep only the union of the two sides. A
	// path deleted in the working tree and one deleted from the index both land
	// in DeletedFiles; len() over any combination of slices therefore cannot tell
	// staged from unstaged, and double-counts every path that appears in two of
	// them. These are computed once, where XY is still intact.
	StagedCount int

	// UnstagedCount is the number of tracked paths whose working tree differs
	// from the index.
	UnstagedCount int

	// TrackedChangedCount is the number of distinct tracked paths with any
	// uncommitted change — staged, unstaged, or conflicted. Untracked and ignored
	// paths are excluded. A path that is both staged and modified counts once
	// here but in each of StagedCount and UnstagedCount, so the two do not sum to
	// this value.
	TrackedChangedCount int
}

Status represents the working tree and staging area status.

type UpdateStrategy

type UpdateStrategy string

UpdateStrategy defines how to handle existing repositories during clone-or-update operations.

const (
	// StrategyRebase rebases local changes on top of remote changes.
	StrategyRebase UpdateStrategy = "rebase"
	// StrategyReset performs a hard reset to match remote state (discards local changes).
	StrategyReset UpdateStrategy = "reset"
	// StrategyClone removes existing directory and performs fresh clone.
	StrategyClone UpdateStrategy = "clone"
	// StrategySkip leaves the existing repository unchanged.
	StrategySkip UpdateStrategy = "skip"
	// StrategyPull performs a standard git pull (merge remote changes).
	StrategyPull UpdateStrategy = "pull"
	// StrategyFetch only fetches remote changes without updating working directory.
	StrategyFetch UpdateStrategy = "fetch"
)

type UserConfig

type UserConfig struct {
	// Name is the user's name.
	Name string

	// Email is the user's email.
	Email string

	// SigningKey is the GPG signing key.
	SigningKey string
}

UserConfig represents user-related Git configuration.

type ValidationError

type ValidationError struct {
	// Field is the field that failed validation.
	Field string

	// Value is the invalid value.
	Value string

	// Reason describes why the value is invalid.
	Reason string
}

ValidationError represents an input validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

func (*ValidationError) Is

func (e *ValidationError) Is(target error) bool

Is implements error comparison.

type WriterLogger

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

WriterLogger wraps an io.Writer as a simple logger. All log levels write to the same writer with level prefixes.

func (*WriterLogger) Debug

func (l *WriterLogger) Debug(msg string, args ...any)

Debug writes a DEBUG-level log message to the writer.

func (*WriterLogger) Error

func (l *WriterLogger) Error(msg string, args ...any)

func (*WriterLogger) Info

func (l *WriterLogger) Info(msg string, args ...any)

Info writes an INFO-level log message to the writer.

func (*WriterLogger) Warn

func (l *WriterLogger) Warn(msg string, args ...any)

Warn writes a WARN-level log message to the writer.

Jump to

Keyboard shortcuts

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