Documentation
¶
Overview ¶
Package git answers what a branch changed and what a working tree is at, by shelling out to the git binary.
It is the framework's public package over framework/internal/service/git (ADR 0158): the value types are aliases of the core git contract's types and the functions delegate straight to the service implementation. What a running program was BUILT from is not here — that is pkg/v1/proc/process's Build and Self, which stay in the SDK; this package asks git about a working tree.
Usage ¶
Resolve the changed set, then ask about it at whichever granularity matters:
import (
"github.com/kitsunium/sdk/framework/git"
)
res := git.Resolve(ctx, git.Config{Root: "/path/to/repo"})
if res.Degraded() {
// res.Reason says why; treat EVERYTHING as in scope.
return
}
if res.Set.ContainsLine(file, line) { /* this line moved */ }
Degrading is not failing, and an empty set is not a degrade ¶
Resolve returns no error. Any 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 — produces a ResolutionValue with FullFallback set and a Reason, never a partial set and never an empty one.
The distinction is the reason this package exists in this shape. "Nothing changed" and "I could not tell what changed" are opposite instructions to the caller, and a resolver that answered the second with an empty set would make a scoped review silently pass on a branch it never examined. Check Degraded before reading Set.
What counts as changed ¶
The set spans four sources, because a review that ignored uncommitted work would contradict what the author sees: the three-dot merge-base delta to HEAD, the index, the working tree, and untracked files. 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, so ContainsFile answers true where ContainsLine answers false for every line.
Config.Include is the caller's policy for which files belong. A nil Include admits everything; a Go tool passes a ".go" suffix test and a generated-file probe. The source implementation hard-coded exactly that pair, and this package deliberately does not: it is one caller's policy, not the domain's.
Which spelling of a path answers ¶
Queries are compared lexically, so two spellings of one file do not match each other — with one exception, and it is the spelling a caller does not choose. git canonicalises the repository root, so pointing Config.Root at a symbolic link records every path under the link's target. Resolve therefore records every entry under BOTH the root you gave and the one git reports, so either answers and a query costs exactly what it did before. An indirection anywhere else in a path you query is still lexical and still does not match.
What a working tree is at ¶
Head answers the question a build description asks about a local module: which commit its working tree is on, when that commit was made, and whether tracked files differ from it.
head, err := git.Head(ctx, "/path/to/module")
if err != nil {
// no repository, no git, or no commit yet: go without.
}
fmt.Println(head.Revision, head.Time, head.Modified)
Modified counts TRACKED files only, staged or not; an untracked file does not make a tree modified. That is narrower than the vcs.modified stamp Go writes into a binary, which counts untracked files too. Nothing is cached, because modified is the one fact here that changes without a commit.
Running against a repository you do not control ¶
Every invocation is hardened against a hostile `.git/config`, which travels with a clone. `core.fsmonitor` and `core.hooksPath` are neutralised with -c (which beats every config file), and `--no-ext-diff` is injected into the subcommands that honour `diff.external` — setting that key empty does not disable it, it makes git try to execute "" and abort. Both were demonstrated executing an attacker-chosen command on a read-only query before the guard, and not after.
A second group executes nothing and is guarded for a different reason. `diff.srcPrefix`, `diff.dstPrefix`, `diff.mnemonicPrefix` and `diff.noprefix` rename the `a/` and `b/` prefixes of a diff header, which files the line ranges under a path nobody queries: measured, ContainsFile stayed true and ContainsLine went false for a line that had just changed. All four are pinned to git's defaults. See the service package's CLAUDE.md for what was deliberately NOT hardened, and why.
Index ¶
Constants ¶
const CodeCommandFailed errs.Code = coregit.CodeCommandFailed
CodeCommandFailed identifies a version-control command that exited non-zero — a corrupted object store, a permission error, a missing binary.
const CodePathAbsent errs.Code = coregit.CodePathAbsent
CodePathAbsent identifies a path that does not exist at the requested commit, which is deliberately distinct from a successful read of an empty file. It is what ShowFile returns when the commit resolves and its tree holds no object at the path.
const CodeRepositoryUnresolved errs.Code = coregit.CodeRepositoryUnresolved
CodeRepositoryUnresolved identifies a path that is not inside a readable repository. Match it with errs.HasCode: it is the one refusal a caller can act on by falling back to a non-VCS path.
Variables ¶
This section is empty.
Functions ¶
func GitDir ¶
GitDir resolves the git directory governing root — the one holding HEAD and the index — and memoizes it per root.
It never assumes `.git` is a directory: in a linked worktree or a submodule it is a `gitdir:` pointer FILE, and this follows it. The result is absolute and cleaned, and failures are never memoized.
The memo is re-validated on every call against two lstats — roughly a thousandth of the subprocess they stand in for — so a repository that moved, one deleted and re-created, and a root that becomes its own repository under a parent one each get a fresh answer. A repository created at a directory BETWEEN root and the worktree top level is not noticed.
func ShowFile ¶
ShowFile returns the content of relPath at commit sha, verbatim.
Its two refusals are different instructions. CodePathAbsent means the commit is readable and holds nothing at that path, which is what tells "deleted" from "emptied" — an empty file is a successful read of "". CodeCommandFailed means anything else: a commit that does not resolve, an unreadable object store, no git. Match with errs.HasCode.
Types ¶
type ChangedSet ¶
type ChangedSet = coregit.ChangedSet
ChangedSet is what a branch changed, queryable by line, file or directory. It aliases the core git port.
type Config ¶
Config is where the repository is and which files the caller counts as changed. It aliases the service type: these are one implementation's construction parameters, which is what ADR 0074 says belongs with the engine.
type HeadState ¶
HeadState is what a working tree is at: the commit HEAD names, its committer date in the offset it was recorded with, and whether a tracked file differs from it. It aliases the service type — the core git port models no commit, so this is one engine's value (ADR 0074).
func Head ¶
Head reports what the working tree containing dir is at: its head commit, that commit's time, and whether tracked files differ from it.
A dir outside any repository — or absent, or on a machine without git — is CodeRepositoryUnresolved. A repository whose HEAD names no commit yet, a bare repository and a failed read are CodeCommandFailed. The three git commands it runs are hardened like every other one here, and the commit time is read from the raw commit object rather than through `git log`, so a planted signature program has nothing to verify.
type Include ¶
type Include = svcgit.IncludeFunc
Include decides which files belong in the changed set. A nil Include admits every file.
type LineRange ¶
type LineRange = coregit.LineRangeValue
LineRange is an inclusive, 1-based run of changed lines. It aliases the core git value type.
type Resolution ¶
type Resolution = coregit.ResolutionValue
Resolution is the outcome of resolving a changed set, including the degraded outcome where none could be trusted. It aliases the core git value type.