gitlog

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 10 Imported by: 0

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

View Source
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

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

func ChangedFileCount(commits []Commit) int

ChangedFileCount counts distinct changed file paths across all commits.

func DefaultConcurrency

func DefaultConcurrency(repoCount int) int

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 DiffFileCount added in v0.2.0

func DiffFileCount(diff string) int

DiffFileCount counts the number of distinct files in a unified diff by counting "diff --git " headers. Used by HeuristicRisk so it scores the already glob-filtered diff, not the unfiltered commit file list.

func DiffLineStats

func DiffLineStats(diff string) (added, removed int)

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

func MissingRequiredCategories(cl Changelog, required []string) []string

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

type AuthorStat struct {
	Author       string
	Commits      int
	LinesAdded   int
	LinesRemoved int
}

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

func CategorizeCommits(commits []Commit) Changelog

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

func ParseLog(out string) ([]Commit, error)

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

func AggregateDigest(commits []Commit, diffs map[string]string, since, until time.Time) Digest

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 FileStat

type FileStat struct {
	Path    string
	Commits int
}

FileStat is the per-file aggregate for a digest (§10.2).

type RepoResult

type RepoResult struct {
	Path   string
	Since  time.Time
	Until  time.Time
	Digest Digest
	Err    error
}

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

func NewRunner(dir string) (*Runner, error)

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) Diff

func (r *Runner) Diff(ctx context.Context, revRange string) (string, error)

Diff runs `git diff` over revRange and returns the raw unified diff text.

func (*Runner) DiffForCommit

func (r *Runner) DiffForCommit(ctx context.Context, hash string) (string, error)

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

func (r *Runner) LatestTag(ctx context.Context) (string, error)

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

func (r *Runner) Log(ctx context.Context, revRange string) ([]Commit, error)

Log runs `git log` over revRange with the control-separator format plus --name-status and parses the output into Commits.

func (*Runner) LogSince

func (r *Runner) LogSince(ctx context.Context, since time.Time) ([]Commit, error)

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.

type TopicStat

type TopicStat struct {
	Topic   string
	Commits int
}

TopicStat is the per-conventional-commit-type aggregate for a digest (§10.2). Topic is the lowercased conventional-commit type, or "other" for non-conventional / unmapped commits.

Jump to

Keyboard shortcuts

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