git

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 10 Imported by: 0

README

git (internal/service/vcs/git)

Implements the core/vcs contract by shelling out to the git binary. Internal service implementation behind the public pkg/v1/git facade — consumers import the facade, not this package.

API

func Resolve(ctx context.Context, cfg Config) corevcs.ResolutionValue
func GitDir(ctx context.Context, root string) (string, error)
func ShowFile(ctx context.Context, repoRoot, sha, relPath string) (string, error)
  • Resolve computes what the branch changed. It returns no error: see below.
  • GitDir resolves the directory holding HEAD and the index, memoized per root. It follows a gitdir: pointer file, so linked worktrees and submodules work.
  • ShowFile reads a blob at a commit. A path absent at that commit is an error, which is what lets a caller tell "deleted" from "emptied".

Resolve degrades, it does not fail

Every condition that prevents a trustworthy answer — no repository, a shallow clone, an unresolved default branch, no merge-base, a git invocation that failed or timed out — yields a ResolutionValue with FullFallback set and a Reason. A partially collected set is discarded rather than returned.

"Nothing changed" and "I could not tell what changed" are opposite instructions to a caller. A resolver that answered the second with an empty set would make a scoped review pass on a branch it never examined.

What the set spans

Four sources, because a review that ignored uncommitted work would contradict what the author sees:

Source Command
the branch's contribution diff <merge-base> HEAD
staged diff --cached
unstaged diff
untracked ls-files --others --exclude-standard

Renames and copies are detected (-M -C). A pure rename or a deletion marks the file and its directory touched while contributing no line range.

Which files count is the caller's

Config.Include decides. Nil admits everything. A Go tool passes a .go suffix test and a generated-file probe — which is what the source implementation hard-coded, and what this package deliberately does not.

Hostile repositories

.git/config travels with a clone and several keys make git execute a command the repository chose. Every invocation goes through runGitOutput, which applies hardenedGitConfig. See CLAUDE.md for what is hardened, what is deliberately not, and the measurements behind both.

Errors

Sentinel When
corevcs.CommandFailed a git subcommand exited non-zero; the command line and stderr travel in Private
corevcs.RepositoryUnresolved the root path could not be made absolute

See also

  • pkg/v1/git — the public facade
  • internal/core/vcs — the ChangedSet port and the value types
  • ADR 0076 — the decision, its alternatives, and what it defers

Documentation

Overview

Package git — the changed set: which files, line ranges and directories a branch touched. The concrete implementation of the core vcs.ChangedSet port.

Package git — the compile-time proof that ChangedSetValue still satisfies the core port.

Hoisted out of changed_set.go per KTN-IFACE-ASSERT-PLACEMENT: the production source carries no purely verificational declarations. Without this assertion a method renamed here would only fail at the call site that still expected the old name, which may be in another repository.

Package git — the resolver's construction parameters.

Package git — parsing git's unified-diff and name-status output into a changed set.

Package git implements the vcs domain by shelling out to the git binary.

There is no VCS library dependency: git's own porcelain is the contract, and every invocation is hardened against a repository that may be hostile — see hardenedGitConfig.

Package git — resolving the git directory that governs a path, once per root.

Package git — resolving what a branch changed versus its merge-base with the default branch.

Package git — reading a file's content at a specific commit.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GitDir

func GitDir(ctx context.Context, root string) (gitDir string, err error)

GitDir resolves the git directory governing root — the directory that holds HEAD and the index — by shelling `git rev-parse --git-dir` once and caching the result per root. It never assumes `.git` is a directory: in linked worktrees and submodules `.git` is a `gitdir:` pointer FILE, and rev-parse follows it to the real per-worktree git directory. The returned path is absolute and cleaned. An error means root is not inside a git repository (or git failed); failures are NOT cached so a later `git init` is picked up on the next call.

func Resolve

func Resolve(ctx context.Context, cfg Config) corevcs.ResolutionValue

Resolve computes the changed set for the repository containing cfg.Root (a directory hint; "" resolves from the current working directory).

It is fail-safe by design: any condition that prevents a trustworthy diff (no repository, shallow clone, unresolved default branch, no merge-base) yields a ResolutionValue with FullFallback=true and a Reason — never an error and never a silent empty diff. The caller degrades to treating everything as in scope, and surfaces Reason.

That asymmetry is the whole design. "Nothing changed" and "I could not tell what changed" are opposite instructions to a caller, and a resolver that returned an empty set for the second would make a review pass on a branch it never looked at.

func ShowFile

func ShowFile(ctx context.Context, repoRoot, sha, relPath string) (content string, err error)

ShowFile returns the content of relPath at commit sha via `git show`. relPath is resolved relative to repoRoot (the "./" prefix pins git's path resolution to the -C directory instead of the repository top level). The error carries git's stderr; a path absent at that commit is an error, letting callers distinguish "absent" from "empty".

The content is returned VERBATIM. It deliberately does not go through runGitOutput, whose trim is right for a SHA or a ref name and wrong for a file: leading and trailing whitespace is the file's own, and a whitespace-only blob would otherwise come back indistinguishable from empty.

Types

type ChangedSetValue

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

ChangedSetValue holds the files, line ranges, and directories that a branch changed versus its merge-base. All paths are absolute and normalised through filepath.Clean so membership tests match the engine's resolved issue positions. Build it with NewChangedSetValue and the add* methods; query it with the Contains* methods.

func NewChangedSetValue

func NewChangedSetValue(repoRoot string) *ChangedSetValue

NewChangedSetValue returns an empty changed-set rooted at the given absolute repository top-level.

func (*ChangedSetValue) ContainsDir

func (s *ChangedSetValue) ContainsDir(absDir string) bool

ContainsDir reports whether absDir directly encloses any touched file. It is NOT recursive: the parent of a touched directory is not itself touched.

func (*ChangedSetValue) ContainsFile

func (s *ChangedSetValue) ContainsFile(absFile string) bool

ContainsFile reports whether absFile appears anywhere in the diff, including as the old side of a rename or a deletion.

func (*ChangedSetValue) ContainsLine

func (s *ChangedSetValue) ContainsLine(absFile string, line int) bool

ContainsLine reports whether line on absFile is within a changed hunk.

func (*ChangedSetValue) IsEmpty

func (s *ChangedSetValue) IsEmpty() bool

IsEmpty reports whether the changed-set contains no touched files at all (clean branch, or a branch whose every change the caller's filter excluded).

type Config

type Config struct {
	// Root is a directory inside the repository to resolve from. Empty resolves
	// from the process working directory.
	Root string
	// Include decides which files belong in the changed set. Nil admits all.
	//
	// This is where a caller states the policy the source implementation had
	// hard-coded: a Go linter passes a test for a ".go" suffix and a
	// generated-file probe; a documentation tool passes something else; a tool
	// that wants everything passes nil.
	Include IncludeFunc
}

Config is what Resolve needs to know: where the repository is, and which files the caller considers part of the change.

Both fields have a meaningful zero and neither is refused (ADR 0031): an empty Root resolves from the process working directory, which is what a tool run inside the repository wants, and a nil Include admits every file, which is the safe direction — a filter defaulting to "exclude" would under-report a diff.

type IncludeFunc

type IncludeFunc func(absPath string) bool

IncludeFunc reports whether the file at absPath belongs in the changed set.

It replaces what the source implementation hard-coded: a ".go" suffix test plus a generated-file probe. Those are one caller's policy, not the domain's — a changed set over Markdown, or one that keeps generated files, is just as legitimate — so the policy is the caller's to state.

A nil IncludeFunc includes everything. That is the safe default in the ADR 0031 sense: the failure mode of including too much is a wider scope, while a default that silently dropped files would under-report a diff, which is the one answer this package must never give.

Jump to

Keyboard shortcuts

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