Documentation
¶
Overview ¶
Package gitlog reads and parses git history via the system `git` binary (os/exec), hidden behind the Source interface so a go-git backend could replace it without touching commands (see docs/TECHNICAL_PLAN.md §4).
Index ¶
- Constants
- Variables
- func ChangedFileCount(commits []Commit) int
- func DefaultConcurrency(repoCount int) int
- func DiffLineStats(diff string) (added, removed int)
- func MissingRequiredCategories(cl Changelog, required []string) []string
- type AuthorStat
- type Changelog
- type ChangelogEntry
- type Commit
- type Digest
- type FileChange
- type FileStat
- type RepoResult
- type Runner
- func (r *Runner) Diff(ctx context.Context, revRange string) (string, error)
- func (r *Runner) DiffForCommit(ctx context.Context, hash string) (string, error)
- func (r *Runner) LatestTag(ctx context.Context) (string, error)
- func (r *Runner) Log(ctx context.Context, revRange string) ([]Commit, error)
- func (r *Runner) LogSince(ctx context.Context, since time.Time) ([]Commit, error)
- type Source
- type TopicStat
Constants ¶
const ( CategoryAdded = "Added" CategoryChanged = "Changed" CategoryDeprecated = "Deprecated" CategoryRemoved = "Removed" CategoryFixed = "Fixed" CategorySecurity = "Security" CategoryOther = "Other" )
Keep a Changelog category names (§9.2). CategoryOther is the catch-all for non-conventional commits and conventional types with no direct mapping (docs/style/test/build/ci/chore).
Variables ¶
var CategoryOrder = []string{ CategoryAdded, CategoryChanged, CategoryDeprecated, CategoryRemoved, CategoryFixed, CategorySecurity, CategoryOther, }
CategoryOrder is the fixed, documented print order for changelog output (§9.4): breaking changes first, then Keep a Changelog categories, Other last.
Functions ¶
func ChangedFileCount ¶
ChangedFileCount counts distinct changed file paths across all commits.
func DefaultConcurrency ¶
DefaultConcurrency returns a sensible worker-pool size for CollectDigests: GOMAXPROCS, capped at the number of repositories (§10.4). Not user-configurable in Этап 3 (no --concurrency flag).
func DiffLineStats ¶
DiffLineStats counts added/removed content lines in a unified diff, ignoring the +++/--- file headers. Shared by the risk heuristic, the offline provider, and the review command's stats block — extracted here (rather than duplicated per caller) since it operates purely on diff text owned by this package.
func MissingRequiredCategories ¶
MissingRequiredCategories reports which of the required category names have zero entries in cl, preserving the input order (§9.3). Unknown category names (typos in policy config) are reported too — CategorizeCommits only ever writes to CategoryOrder keys, so an unknown name is always "missing".
Types ¶
type AuthorStat ¶
AuthorStat is the per-author aggregate for a digest (§10.2).
type Changelog ¶
type Changelog struct {
// Categories maps every CategoryOrder name to its entries (possibly
// empty, but the key is always present — §9.4 JSON contract).
Categories map[string][]ChangelogEntry
// Breaking is the ordered list of breaking-change entries, duplicated
// from their home category (§9.2).
Breaking []ChangelogEntry
}
Changelog is the categorized result of a commit range (§9.2, §9.4).
func CategorizeCommits ¶
CategorizeCommits classifies commits into Keep a Changelog categories (§9.2). Merge commits (Subject starting with "Merge ") are excluded entirely. Duplicate hashes keep only the first occurrence.
type ChangelogEntry ¶
type ChangelogEntry struct {
Hash string // short (7-char) hash, per §9.4 uniform styling
Subject string // subject with the "type(scope)!:" prefix stripped
Category string
Breaking bool
// BreakingText is the callout text used in the "⚠ BREAKING CHANGES"
// section: the first line after "BREAKING CHANGE:" in the body, or the
// stripped subject if the only marker was "!". Empty when !Breaking.
BreakingText string
}
ChangelogEntry is one commit rendered into a changelog category.
type Commit ¶
type Commit struct {
Hash string
Author string
Date time.Time
Subject string
Body string
Files []FileChange
}
Commit is a single commit parsed from `git log` output.
func ParseLog ¶
ParseLog parses the output of
git log --pretty=format:%H%x1f%an%x1f%aI%x1f%s%x1f%b%x1e --name-status <range>
Records are split on 0x1E and fields on 0x1F — never on "\n", because commit bodies legitimately contain newlines. The record separator sits at the end of the pretty format, so the --name-status block of commit N appears *after* N's record separator, i.e. at the start of the next chunk; the parser attributes it back to the previous commit.
type Digest ¶
type Digest struct {
Since time.Time
Until time.Time
Commits int
FilesChanged int
LinesAdded int
LinesRemoved int
ByAuthor []AuthorStat
ByTopic []TopicStat
TopFiles []FileStat
}
Digest is the deterministic aggregation result for one repository over one time window (§10.2, §10.3).
func AggregateDigest ¶
AggregateDigest computes the deterministic digest for commits observed in [since, until]. diffs maps commit hash to that commit's unified diff text (collected by the caller via Runner.DiffForCommit) — used only for the per-author added/removed line counts.
type FileChange ¶
type FileChange struct {
// Status is the raw git status letter(s): "A", "M", "D", "R100", "C75", ...
Status string
// Path is the (new) path of the file.
Path string
// Old is the previous path for renames/copies (R/C statuses); empty otherwise.
Old string
}
FileChange is one entry of a `--name-status` block.
type RepoResult ¶
RepoResult is the per-repository outcome of a multi-repo digest collection (§10.4). Exactly one of Digest/Err is meaningful: when Err is non-nil, the repo failed (bad path, not a git repo, git failure) and the other fields besides Path/Since/Until are zero values — callers must check Err before reading Digest.
func CollectDigests ¶
func CollectDigests(ctx context.Context, repoPaths []string, since time.Time, concurrency int) []RepoResult
CollectDigests runs AggregateDigest over each repository path concurrently, using a bounded worker pool of `concurrency` goroutines communicating over a job-index channel (§10.4) — not an unbounded fan-out, and not a third-party errgroup, per the project's "teach raw stdlib concurrency" principle (docs/TECHNICAL_PLAN.md §2).
Results are written to results[i] by exactly one goroutine (the one that claims job i), so no mutex is needed, and the returned slice preserves the input order of repoPaths regardless of completion order — required for deterministic output and golden tests.
A single bad repository (missing directory, not a git repository, git failure, or "no commits in window") never aborts the others: errors are isolated into that repo's RepoResult.Err. Cancelling ctx (e.g. Ctrl-C) stops workers from starting new repos; already-running `git` invocations are killed by exec.CommandContext as usual.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner implements Source by shelling out to the system `git` binary.
func NewRunner ¶
NewRunner returns a Runner operating in dir (empty = current directory). It fails with a friendly error if `git` is not installed in PATH.
func (*Runner) DiffForCommit ¶
DiffForCommit runs `git show --format= <hash>` and returns the unified diff introduced by that single commit (no commit message, just the diff body).
func (*Runner) LatestTag ¶
LatestTag runs `git describe --tags --abbrev=0` and returns the most recent reachable tag. When the repository has no tags, git exits non-zero; that specific case is not an error here (§9.5) — LatestTag returns ("", nil) so callers can branch on an empty string instead of inspecting error text. Any describe failure (no tags, unborn HEAD, ...) degrades to "no tag" — the exact stderr wording varies by git version/locale, so it is not worth pattern-matching; every failure means the same thing to the caller.
func (*Runner) Log ¶
Log runs `git log` over revRange with the control-separator format plus --name-status and parses the output into Commits.
func (*Runner) LogSince ¶
LogSince runs `git log --since=<RFC3339>` with the control-separator format plus --name-status and parses the output into Commits (§10.1). The window is open-ended on the upper bound (implicitly "since..HEAD" of the current checkout), matching every other command's branch-agnostic behavior.
type Source ¶
type Source interface {
// Log returns the commits in the given revision range (e.g. "HEAD~5..HEAD").
Log(ctx context.Context, revRange string) ([]Commit, error)
// Diff returns the unified diff text for the given revision range.
Diff(ctx context.Context, revRange string) (string, error)
// LatestTag returns the most recent reachable tag from HEAD (like
// `git describe --tags --abbrev=0`). An empty string with a nil error
// means no tags exist — a legitimate result, not a failure (see
// docs/TECHNICAL_PLAN.md §9.5).
LatestTag(ctx context.Context) (string, error)
// LogSince returns the commits reachable from HEAD committed after since
// (like `git log --since=<RFC3339>`).
LogSince(ctx context.Context, since time.Time) ([]Commit, error)
// DiffForCommit returns the unified diff text introduced by a single
// commit (like `git show --format= <hash>`).
DiffForCommit(ctx context.Context, hash string) (string, error)
}
Source provides git history for a revision range. It exists so that the os/exec-based Runner could later be swapped for a go-git implementation without touching command code.