gitutil

package
v0.7.186 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotRegularFile reports a changed path that is neither a regular file
	// nor absent — most often a dirty submodule, whose state lives in another
	// repository and so cannot be hashed from here.
	ErrNotRegularFile = errors.New("changed path is not a regular file")
	// ErrDigestBudget reports that the changed files total more than the digest
	// budget, the point past which hashing them costs more than whatever the
	// caller would have skipped.
	ErrDigestBudget = errors.New("changed files exceed the digest budget")
)

Errors reported by Fingerprint. They name the condition that stopped it, because a caller that silently stops caching for one repo and not another is otherwise impossible to explain: each of these means "this tree cannot be fingerprinted at all", not "nothing has changed".

View Source
var ErrCountBudget = errors.New("changed files exceed the line count budget")

ErrCountBudget reports that the changed files hold more content than the line count is willing to read.

View Source
var ErrMergeTreeUnsupported = errors.New("git merge-tree --write-tree is unavailable (requires git 2.38+)")

ErrMergeTreeUnsupported reports a git too old for `merge-tree --write-tree`, which landed in git 2.38. Callers treat it as "cannot answer" rather than "no conflicts": the two are opposite advice, and reporting a clean merge from a git that never performed one is the worse of the two mistakes.

Functions

func ChangesBetween added in v0.7.186

func ChangesBetween(dir, base string) (changeset.Changes, error)

ChangesBetween measures the working tree at dir against an earlier snapshot.

Both sides are tree objects, so the comparison is exact and says nothing about commits: a commit landing in between moves HEAD but not the content, and this reports the same change either way. That is the point. Measuring against HEAD means the number only ever grows until something commits — five small turns read as one large change by the fifth — and resets to nothing the moment anything commits, validated or not.

Renames are not detected. A renamed file reports as one path gone and another arrived, which counts double and reads as less inert than it is — the conservative direction, and the cheaper one.

The error is non-nil when base cannot be diffed: it has been garbage collected, or the tree cannot be snapshotted now. Callers fall back to WorkingChanges.

func CurrentBranchIn added in v0.7.160

func CurrentBranchIn(dir string) (string, error)

CurrentBranchIn returns the current git branch name for the repo rooted at dir. Returns an error if in detached HEAD state or not in a git repo.

func DefaultBranchIn added in v0.7.174

func DefaultBranchIn(dir string) (string, error)

DefaultBranchIn returns the default branch of the repo rooted at dir — the branch a PR merges into. It reads the remote HEAD symref git records at clone time, trying origin before upstream so a fork checkout whose canonical remote is upstream still resolves. An error is a routine answer, not a failure: a repo created with `git init` and pushed has no remote HEAD at all, and callers are expected to fall back rather than treat this as fatal.

func DefaultRemoteBranchIn added in v0.7.186

func DefaultRemoteBranchIn(dir string) (remote, branch string, err error)

DefaultRemoteBranchIn returns the remote and branch name of the default branch for the repo rooted at dir — the branch a PR merges into, and the remote it lives on.

The remote matters to anything that needs to refresh the ref rather than just name it: `origin/main` and `upstream/main` are different commits in a fork, and fetching the wrong one leaves the comparison stale in exactly the checkout where being stale is most misleading.

Like DefaultBranchIn, an error is a routine answer: a repo with no remote HEAD recorded has no default branch to find, and callers fall back rather than fail.

func FetchRemoteBranch added in v0.7.186

func FetchRemoteBranch(ctx context.Context, dir, remote, branch string) error

FetchRemoteBranch updates the remote-tracking ref for one branch.

The refspec is explicit rather than relying on `git fetch <remote> <branch>` opportunistically updating refs/remotes/<remote>/<branch>: that update depends on the remote's configured fetch refspec matching, and a checkout with a narrowed refspec would fetch successfully and leave the tracking ref untouched — a silent no-op that looks like a fetch.

Only the one branch is fetched, and no tags, because this runs on a timer in the background. The cost of a full fetch is somebody else's bandwidth and a pack the developer did not ask for.

func HeadRef added in v0.7.122

func HeadRef(cwd string) (string, error)

HeadRef returns the SHA of the current HEAD commit in the repo at cwd.

func HeadRefCtx added in v0.7.138

func HeadRefCtx(ctx context.Context, cwd string) (string, error)

HeadRefCtx returns the SHA of the current HEAD commit in the repo at cwd, honouring ctx for cancellation/timeout.

func MaterializeTree added in v0.7.186

func MaterializeTree(dir, tree string) (string, func(), error)

MaterializeTree checks out a tree snapshot into its own directory and returns the path, along with a function that removes it.

This is what lets checks run against a state that cannot move underneath them. A run against the live working tree is racing the developer and the agent: anything they edit while it runs makes its answer describe code that is no longer there, and that answer then has to be thrown away. A run against a checked-out snapshot is true about that snapshot however long it takes, and stays true afterwards.

What it does not carry is everything git was told to ignore. Dependencies, build caches, local env files and anything else gitignored are absent, so checks that need them fail here for reasons that have nothing to do with the change. That is why the worktree mode is opt-in: for some projects it is exactly right, and for others every run would report an environment failure as a code failure.

Unlike SnapshotTree this does touch repository metadata — git records the worktree under .git/worktrees — which the cleanup removes. A daemon killed before cleanup leaves an entry behind; `git worktree prune` clears those, and the next cleanup runs it.

func RepoRoot

func RepoRoot(from string) (string, error)

RepoRoot returns the root directory of the current git repository by walking up from the given directory looking for .git/.

func RevParseCtx added in v0.7.186

func RevParseCtx(ctx context.Context, dir, rev string) (string, error)

RevParseCtx resolves a revision to its SHA in the repo at dir. An error means the revision does not exist, which for a remote-tracking ref is the ordinary state of a repo that has never fetched it.

func SnapshotTree added in v0.7.186

func SnapshotTree(dir string) (string, error)

SnapshotTree captures the whole working state at dir as a git tree object and returns its SHA: tracked files, staged edits and untracked files alike, minus whatever .gitignore excludes.

It exists so a caller can ask "what has changed since this state" later on, about a state that was never committed. A fingerprint cannot answer that — it says whether the tree is the same tree, not what moved — and HEAD cannot either, since the state worth comparing against is usually the last one that passed its checks rather than the last one somebody committed.

Nothing about the developer's repository moves. The index is a throwaway copy in a temp file (GIT_INDEX_FILE), so no staging is touched, and HEAD and the working tree are never written. The copy is seeded from the real index for its stat cache: without it every file would be re-hashed on every call, which on a large repo is the difference between milliseconds and seconds.

It does write blob and tree objects into .git/objects. They are unreferenced, so git's own gc collects them in its own time — which is also why a snapshot is not a durable handle. A caller holding one that has since been collected gets an error from ChangesBetween and should fall back to measuring against HEAD.

func TopLevelCtx added in v0.7.138

func TopLevelCtx(ctx context.Context, dir string) string

TopLevelCtx returns the git repository root for dir, or "" if not in a git repo, honouring ctx for cancellation/timeout.

func WorkingChanges added in v0.7.186

func WorkingChanges(dir string) (changeset.Changes, error)

WorkingChanges measures the working tree at dir against HEAD.

Tracked files are measured with `git diff --shortstat HEAD`, which counts staged and unstaged edits together — the same total a developer would see running the command by hand. Untracked files are counted here instead, because git will not diff a file it does not know about, and a new file is exactly the change a caller most wants counted.

The error is non-nil when the tree's size cannot be established: dir is not a repo, the repo has no commits yet, or the untracked content exceeds the read budget. The returned Changes is then the zero value, which reports no paths and no lines — never mistake it for a small change, since it is the same value a clean tree produces.

Types

type MergePreview added in v0.7.186

type MergePreview struct {
	// Clean is true when the merge produced a tree with no conflicts.
	Clean bool
	// Paths lists the conflicted paths, empty when Clean. Only paths git
	// reported as conflicted appear here — a file merged automatically is not
	// listed even though the merge rewrote it.
	Paths []string
}

MergePreview is the result of merging two commits without touching the working tree, index, or HEAD.

func PreviewMerge added in v0.7.186

func PreviewMerge(ctx context.Context, dir, ours, theirs string) (MergePreview, error)

PreviewMerge reports whether ours and theirs merge cleanly, using `git merge-tree --write-tree`.

Nothing in the repository is modified: merge-tree resolves the merge entirely in the object database and writes the result as a loose tree that nothing references, so this is safe to run against a checkout somebody is actively working in. That is the whole reason this is the primitive rather than a throwaway worktree and a real `git merge` — a background process must never be able to disturb a developer's index.

Both arguments are commit-ish. The merge is of committed history only: uncommitted work in the tree at dir is invisible to it, so a conflict that exists only in unstaged edits is not reported. Callers surfacing this to a person need to say so, or the silence reads as an all-clear it did not check.

A conflicted merge is a successful call, not an error — the answer is in Clean. An error means the question could not be answered at all.

type Worktree added in v0.7.144

type Worktree struct {
	// Head is the SHA of the current HEAD commit.
	Head string
	// Digest hashes every path git reports as changed, contents included.
	Digest string
	// Clean reports whether git sees no change at all relative to HEAD: nothing
	// modified, staged, or untracked.
	Clean bool
}

Worktree fingerprints the state of a git working tree at a point in time. Two Worktrees with equal Head and Digest describe trees with identical content, so callers can compare one against a previously recorded value to decide whether anything has changed since.

The zero Worktree describes no tree at all. It reports the tree as not clean and is refused as a cache key, so a caller holding one falls back to doing whatever work it might otherwise have skipped. Fingerprint returns it alongside an error saying which condition made the tree unfingerprintable.

func Fingerprint added in v0.7.144

func Fingerprint(dir string) (Worktree, error)

Fingerprint captures the state of the working tree at dir: the HEAD commit plus a digest of the porcelain status entries — which record adds, deletions, renames and mode changes — and the current contents of every changed path.

Hashing contents is what makes the fingerprint sensitive to repeated edits. Porcelain output for a modified file is byte-identical before and after a further edit (both report " M path"), so status alone would call the tree unchanged across that edit — exactly the loop a Stop hook runs in.

The error is non-nil when the tree's state cannot be established: dir is not a repo, the repo has no commits yet, a changed path cannot be read, or the tree exceeds the digest budget. The returned Worktree is then the zero value, which is safe to pass on: no caller can mistake it for a real tree.

Jump to

Keyboard shortcuts

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