gogit

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 14 Imported by: 0

README

gogit

Go CI Go Lint Go SAST Docs Docs Visualization License

Generic, dependency-light Git ergonomics for Go, by shelling out to the git CLI: repository discovery, commit-log parsing with trailers (Co-authored-by) and change stats, AI authorship detection, conventional commit parsing, parallel multi-repo execution, calendar-date filtering, branch/origin metadata, remote-URL normalization, and tag dates. The base layer for higher-level tools — including the bundled gitscan CLI and the OmniDevX telemetry collectors — in the same way gogithub underlies GitHub integrations.

repo, _ := gogit.Open("/path/to/repo")
commits, _ := repo.Log(ctx, gogit.LogOptions{
    Since:        weekStart,
    IncludeStats: true,
    Reverse:      true,
})
for _, c := range commits {
    attr := gogit.AnalyzeAuthorship(c)
    fmt.Println(c.Hash, c.Author.Email, attr.Tools, c.Insertions)
}

Library Features

Feature Entry Point Description
Discovery Discover(roots, maxDepth) Depth-bounded repository discovery
Commit log Repo.Log(ctx, LogOptions) Parsed commits with dates, trailers, numstat
Incremental log LogOptions.SinceCommit High-water-mark ingestion (sha..HEAD)
Reverse order LogOptions.Reverse Chronological (oldest-first) iteration
Co-authors Commit.CoAuthors() Co-authored-by trailer extraction
AI authorship AnalyzeAuthorship(c) Detect AI tools, models, and human co-authors
AI provider registry DefaultAITools Claude Code, Copilot, Gemini CLI, Cursor, Aider
AI co-author parsing Commit.AICoAuthors() Identify AI tools with model version extraction
Commit stats Repo.CollectCommitStats Commit/LOC aggregation by conventional-commit category
Multi-repo stats AggregateCommitStats Parallel aggregation with AI-assisted metrics
Conventional commits ParseConventionalCommit(s) Type, scope, breaking flag, subject
Trailer lookup Commit.TrailerValue(key) Case-insensitive trailer access
Parallel execution RunAll(ctx, paths, fn, workers) Generic concurrent multi-repo operations
Progress reporting RunAllWithProgress(...) Parallel execution with progress callback
Metadata Repo.Branch, Repo.OriginURL Branch name and remote URL
Remote normalization NormalizeRemoteURL(url) Canonical host/path identifiers
Tags Repo.Tags, Repo.TagsWithDates Tag listing with creation dates
Pending commits Repo.PendingCommits(ctx, sinceCommit) Commits ahead of upstream, or after an explicit commit hash
Upstream check Repo.HasUpstream(ctx) Whether the current branch has an upstream configured

Renamed from gitscan (the CLI lives on at cmd/gitscan).

gitscan CLI

A CLI tool to scan multiple Git repositories and identify repos that need attention. Helps prioritize which repos to update, commit, and push.

Installation

Homebrew (macOS/Linux)
brew tap grokify/tap
brew install gitscan
Go Install
go install github.com/grokify/gogit/cmd/gitscan@latest
Build from Source
git clone https://github.com/grokify/gogit.git
cd gogit
go build -o gitscan ./cmd/gitscan

Usage

gitscan [directory]              # Scan for issues (defaults to current dir)
gitscan since <duration> [dir]   # Filter by modification time
gitscan dep <module> [dir]       # Filter by dependency
gitscan order [dir]              # Show repos in dependency order
gitscan pending [path...]        # List unpushed commits, across one or many repos
gitscan pushed [count] [dir]     # List the most recent pushed commits

The scan directory is a positional argument that defaults to the current directory; there is no -d/--dir flag.

Root Command (Issue Scanning)

Scan repos for uncommitted changes, replace directives, and module mismatches:

gitscan ~/go/src/github.com/grokify

The directory to scan is the (optional) positional argument, defaulting to the current directory.

Flag Short Default Description
--format -f list Output format: list or table
--show-clean false Show repos with no issues
--summary true Show summary at the end
--check-workflows false Check GitHub Actions workflow compliance against a reference repo
--ref-repo plexusone/.github Reference workflow repository for --check-workflows
Examples
# Scan all repos in a directory
gitscan ~/go/src/github.com/grokify

# Output as markdown table (compact view)
gitscan -f table ~/go/src/github.com/grokify

# Show all repos including clean ones
gitscan --show-clean ~/projects

Since Subcommand

Filter repos by modification time, with optional dependency filtering:

gitscan since <duration> [directory]
Flag Short Default Description
--dep (none) Also filter by dependency (AND logic)
--unpushed -u false Only show repos with uncommitted changes or unpushed commits (AND logic)
--recurse -r false Check nested go.mod files

Duration formats: 7d (days), 2w (weeks), 1m (months), 24h (hours). directory defaults to the current directory.

Since Examples
# Repos modified in last 7 days
gitscan since 7d ~/go/src/github.com/grokify

# Repos modified in last 7 days AND depending on a module
gitscan since 7d --dep github.com/grokify/mogo ~/go/src/github.com/grokify

Dep Subcommand

Filter repos by dependency on a specific module:

gitscan dep <module> [directory]
Flag Short Default Description
--recurse -r false Check nested go.mod files
--direct-only -D false Only match direct requirements (excludes // indirect)
--prefix false Match module path as a prefix (e.g. to match any major version)
Dep Examples
# Find repos depending on a module
gitscan dep github.com/grokify/mogo ~/go/src/github.com/grokify

# Include nested go.mod files (monorepos)
gitscan dep github.com/grokify/mogo -r ~/go/src/github.com/grokify

# Only repos that require the module themselves, not just transitively
gitscan dep github.com/google/go-github/v88 ~/go/src/github.com/grokify --direct-only

# Match any major version of a module in one pass
gitscan dep github.com/google/go-github ~/go/src/github.com/grokify --prefix --direct-only

Pending Subcommand

Report commits that are ahead of their upstream — not yet pushed — across one or more repositories. Useful for pre-push review, for a morning sweep of everything you have waiting to push, or for feeding an agent a structured list of what's about to go out.

gitscan pending [path...]

Each path is either a git repository (reported directly) or a directory whose repositories are discovered (one level deep by default; use --depth to go deeper) and each reported in turn. Pass several paths — e.g. one per GitHub org you manage — to sweep them together. With no path, the current directory is used.

Flag Short Default Description
--since-commit (none) List commits after this hash instead of unpushed commits (single repo only)
--format -f table Output format: table (aligned, for terminals), markdown (copy-pasteable), or json
--tz original Timestamp timezone: original (as recorded by git, per-commit), local (this machine's timezone), or utc
--depth 1 How many directory levels below each path to search for repositories

By default the baseline for "pending" is each branch's push target — its configured upstream, or the matching remote-tracking branch (e.g. origin/main). A branch that was never pushed has no such target, so all of its commits are reported as pending (rather than erroring). Use --since-commit to list commits after a specific hash instead; that applies to a single repository only.

The output shape is invariant in the number of repositories: a single repo is just a fleet of one. In a multi-repo sweep, repositories with nothing pending are omitted from the table/markdown views (the summary still counts them), and progress is shown on stderr so stdout stays clean for piping and JSON.

Timestamps are RFC 3339 with an explicit UTC offset (Z for exact UTC, otherwise numeric, e.g. -07:00). By default each commit keeps its own recorded timezone; --tz utc or --tz local converts every timestamp to one consistent zone.

Pending Examples
# Unpushed commits in the current repo
gitscan pending

# ...in another repo
gitscan pending ~/go/src/github.com/me/repo

# Sweep every repo across several orgs you manage
gitscan pending ~/go/src/github.com/{myorg,myuser}

# Commits after a specific hash (single repo)
gitscan pending --since-commit abc1234

# Copy-pasteable markdown table (e.g. for a PR description)
gitscan pending --format markdown

# Machine-readable output for agents
gitscan pending --format json

# Normalize all timestamps to UTC (or --tz local for this machine's timezone)
gitscan pending --tz utc
Pending Output

Table format (default; aligned columns via text/tabwriter, meant to be read directly in a terminal). A multi-repo sweep prints one section per repo with pending work, then a summary:

Repo: /Users/me/go/src/github.com/myorg/service-a
Pending commits (not yet pushed to @{upstream}): 2

#  HASH     DAY  TIMESTAMP                  MESSAGE
1  1fbde76  Mon  2026-09-07T12:16:02-07:00  feat: add b
2  af0101e  Mon  2026-09-07T12:16:05-07:00  feat: add c

Repo: /Users/me/go/src/github.com/myorg/service-b
Pending commits (no upstream configured; all local commits unpushed): 1

#  HASH     DAY  TIMESTAMP                  MESSAGE
1  9c1d2e0  Tue  2026-09-08T09:03:11-07:00  feat: initial import

Summary: 42 repos scanned, 2 with unpushed commits, 3 commits total

Markdown format (--format markdown; valid GitHub-flavored markdown, e.g. for pasting into a PR description or issue):

| # | Hash | Day | Timestamp | Message |
|---|------|-----|-----------|---------|
| 1 | 1fbde76 | Mon | 2026-09-07T12:16:02-07:00 | feat: add b |
| 2 | af0101e | Mon | 2026-09-07T12:16:05-07:00 | feat: add c |

JSON format (--format json) is always a repos array plus a summary, whether one repository or many — so consumers never branch on repo count:

{
  "repos": [
    {
      "repo": "/Users/me/go/src/github.com/myorg/service-a",
      "mode": "unpushed",
      "ref": "@{upstream}",
      "count": 2,
      "commits": [
        {
          "hash": "1fbde76e3b4c9ff29974e52b2e67bcece53ddaf6",
          "weekday": "Mon",
          "date": "2026-09-07",
          "time": "12:16:02",
          "timestamp": "2026-09-07T12:16:02-07:00",
          "message": "feat: add b"
        }
      ]
    }
  ],
  "summary": {
    "reposScanned": 42,
    "reposWithCommits": 2,
    "commitsTotal": 3
  }
}

The mode field is one of unpushed (ahead of the push baseline in ref), unpushed-all (no push target — every local commit is pending), or since-commit (commits after the ref hash).

Pushed Subcommand

The counterpart to pending: list the most recent commits already pushed on a repository's current branch — those reachable from its push target (upstream or matching remote-tracking branch) — newest first. Together, pending and pushed show the recent commits on either side of what has reached the remote.

gitscan pushed [count] [directory]

count is the number of commits to show and defaults to 10; directory defaults to the current directory. Either positional may be given in either order (the numeric one is the count). When the branch has no push target, nothing is reported as pushed.

Flag Short Default Description
--format -f table Output format: table, markdown, or json
--tz original Timestamp timezone: original, local, or utc
Pushed Examples
# Last 10 pushed commits in the current repo
gitscan pushed

# Last 25 pushed commits
gitscan pushed 25

# ...in another repo (count and directory in either order)
gitscan pushed 25 ~/go/src/github.com/me/repo

# Machine-readable output for agents
gitscan pushed --format json
Pushed Output
Repo: /Users/me/go/src/github.com/me/repo
Pushed commits (most recent first, from @{upstream}): 3

#  HASH     DAY  TIMESTAMP                  MESSAGE
1  90da58c  Mon  2026-09-07T20:41:29-07:00  fix(release): target the current repo name in goreleaser config
2  882488a  Mon  2026-09-07T19:43:37-07:00  docs: update changelog for the full commit range
3  d5f5fa1  Mon  2026-09-07T18:12:04-07:00  feat(cmd): show RFC3339 timestamps with weekday

pushed shares pending's markdown and json formats (the same invariant repos + summary envelope, with mode set to pushed).

Order Subcommand

Show repos in topological dependency order - dependencies first, then dependents. Helps determine the correct order to update and release Go modules.

gitscan order [directory]

directory is a positional argument that defaults to the current directory.

Flag Short Default Description
--since -s (none) Filter repos modified within duration
--transitive -t false Include repos that transitively depend on modified repos
--unpushed -u false Only show repos with uncommitted changes or unpushed commits
Order Examples
# Show all repos in dependency order
gitscan order ~/go/src/github.com/grokify

# Repos modified in last 7 days, in dependency order
gitscan order -s 7d ~/go/src/github.com/grokify

# Include transitive dependents (repos depending on modified repos)
gitscan order -s 7d -t ~/go/src/github.com/grokify

# Only show repos that need to be pushed
gitscan order -s 7d -t -u ~/go/src/github.com/grokify
Order Output
Update order (dependencies first):
----------------------------------
  1. mogo                  2026-02-08 12:28
  2. gogithub              2026-02-07 08:09 (depends on: mogo)
  3. goauth                2026-02-09 19:38 (depends on: mogo)
  4. gogoogle              2026-02-09 17:31 (depends on: goauth, mogo)
  5. go-aha                2026-02-09 02:15 (depends on: goauth, gogoogle, mogo)

Total: 5 repos in dependency order

Checks Performed

For each direct subdirectory, gitscan checks:

  1. Uncommitted Changes - Detects modified, added, or deleted files using git status --porcelain

  2. Replace Directives - Parses go.mod for replace directives (both single-line and block format), which may indicate local development dependencies that shouldn't be committed

  3. Module Name Mismatch - Compares the module name in go.mod with the directory name to identify renamed or copied repos

  4. Unpushed Commits - Detects commits that haven't been pushed to remote (with -u flag)

Output Format

During scanning, a progress bar shows real-time status:

Scanning: /Users/you/go/src/github.com/grokify
Found 584 directories to scan

[████████████████░░░░░░░░░░░░░░░░░░░░░░░░]  42% (245/584) my-current-repo
List Format (default)

Repos are shown in a numbered list with issues and internal dependencies:

  1. mogo                  2026-02-08 12:28
  2. gogithub              2026-02-07 08:09 (depends on: mogo)
  3. my-service            2026-02-10 15:30 [uncommitted, replace:2]

Summary: 100 repos scanned, 25 modified within 7d
Table Format (-f table)

Compact markdown table with one repo per row:

| # | Repository | Uncommitted | Replace | Mismatch | Git | go.mod |
|---|------------|-------------|---------|----------|-----|--------|
| 1 | omnistorage |  |  | X | Y | Y |
| 2 | omnistorage-github | X |  |  | Y | - |
| 3 | structured-changelog | X |  |  | Y | Y |
| 5 | structured-roadmap |  | 5 |  | - | Y |

Column legend:

  • Uncommitted: X = has uncommitted changes
  • Replace: number of replace directives in go.mod
  • Mismatch: X = module name doesn't match directory
  • Git: Y = is a git repo, - = not a git repo
  • go.mod: Y = has go.mod, - = no go.mod

Finding Dependents

When making breaking changes to a library, find all local repos that depend on it:

# Find repos depending on a module
gitscan dep github.com/grokify/gogithub ~/go/src/github.com/grokify

# Include nested go.mod files (monorepos, nested modules)
gitscan dep github.com/grokify/gogithub -r ~/go/src/github.com/grokify

# Find recently modified repos that depend on a module
gitscan since 7d --dep github.com/grokify/mogo ~/go/src/github.com/grokify

Performance

gitscan uses parallel scanning with a goroutine worker pool (defaults to GOMAXPROCS workers) for fast scanning of large directory trees. Expensive operations like modification time calculation and unpushed commit detection are performed lazily only when needed.

Why the Git CLI, Not go-git

gitscan shells out to the git binary for repository status checks rather than using the pure-Go go-git library:

Backend Speed Compatibility
git CLI (used) Fast (~2.5s for 600 repos) Full compatibility with git's index/fsmonitor optimizations
go-git (not used) Slower (~10s for 600 repos) Pure Go, but must re-implement status/porcelain semantics in userspace

go-git's main selling point — no dependency on a git binary — doesn't apply here: gitscan's entire job is scanning directories that are already git repositories, so a working git install is a given. Given that, the CLI backend's speed and exact compatibility with real git semantics (ahead/behind counts, detached HEAD, porcelain edge cases) outweigh go-git's portability benefit, so gitscan doesn't carry a second backend to build and keep bug-for-bug identical to the first.

Cold vs. Warm Cache

For a fleet-wide sweep (e.g. gitscan pending across several org directories), the dominant cost is the OS reading each repository's .git metadata, not gitscan's own work. Measured across ~640 repositories:

  • First run of the session (cold filesystem cache): ~22s — mostly disk I/O paging in git metadata.
  • Subsequent runs (warm cache): ~5.5s — already close to the floor of one git process spawn per repository.

Extra worker parallelism does not help here: the run is bounded by per-repository git startup, not CPU. Reducing the number of git invocations per repository would trim warm runs modestly but has little effect on the cold-cache first run, since that data must be read from disk regardless.

Future: Skip Unchanged Repositories

The highest-leverage speedup for repeated fleet sweeps is to avoid visiting repos that cannot have changed. A --modified-since <duration> prefilter (reusing the duration parsing already used by gitscan since) would stat each repository's .git and skip any untouched within the window before spawning git at all. On a typical day only a handful of a large fleet's repos have recent activity, so this could cut the working set — and the wall time — by 10–50x. This is a planned enhancement, not yet implemented.

Use Cases

  • Pre-push audit: Identify repos with uncommitted work before leaving for vacation
  • Dependency cleanup: Find repos with local replace directives that need resolution
  • Repo hygiene: Detect copied/renamed repos with mismatched module names
  • Breaking changes: Find all repos to update before releasing library changes
  • Security patches: Locate repos using vulnerable dependencies
  • Release ordering: Determine correct order to update and release interdependent modules
  • Prioritization: Focus on repos that need immediate attention

License

MIT

Documentation

Overview

Package gogit provides generic, dependency-light Git ergonomics by shelling out to the git CLI: repository discovery, commit-log parsing with trailers and change stats, and repository metadata (branch, origin URL).

It is the base layer for higher-level tools — the gitscan CLI (cmd/gitscan) and domain collectors such as OmniDevX — in the same way github.com/grokify/gogithub underlies GitHub integrations.

Index

Constants

This section is empty.

Variables

View Source
var DefaultAITools = []AIToolPattern{
	{
		Name:     "Claude Code",
		Provider: "anthropic",
		Emails:   []string{"noreply@anthropic.com"},

		ModelPattern: regexp.MustCompile(`(?i)^Claude\s+(\S+\s+[\d][\d.]*)$`),
	},
	{
		Name:     "GitHub Copilot",
		Provider: "github",
		Emails:   []string{"noreply@github.com", "copilot@github.com"},

		ModelPattern: nil,
	},
	{
		Name:     "Gemini CLI",
		Provider: "google",
		Emails: []string{
			"218195315+gemini-cli@users.noreply.github.com",
			"176961590+gemini-code-assist[bot]@users.noreply.github.com",
			"gemini-cli-agent@google.com",
			"gemini@google.com",
		},

		ModelPattern: regexp.MustCompile(`(?i)^gemini[-_]?cli\s+(.+)$`),
	},
	{
		Name:     "Cursor",
		Provider: "cursor",
		Emails:   []string{"ai@cursor.sh", "cursor@cursor.sh"},

		ModelPattern: regexp.MustCompile(`(?i)^Cursor\s+(.+)$`),
	},
	{
		Name:         "Aider",
		Provider:     "aider",
		Emails:       []string{"aider@aider.chat"},
		ModelPattern: nil,
	},
}

DefaultAITools is the canonical, built-in registry of AI coding assistants and their co-author signatures. Consumers should use MatchAITool or Commit.AICoAuthors rather than maintaining separate lists.

Functions

func Discover

func Discover(roots []string, maxDepth int) ([]string, error)

Discover walks each root up to maxDepth directory levels (1 = direct children) and returns paths that are git repositories. Discovery does not descend into repositories, so nested checkouts and vendored trees are not double-counted.

func IsRepo

func IsRepo(path string) bool

IsRepo reports whether path contains a .git entry (directory or gitfile).

func NormalizeRemoteURL

func NormalizeRemoteURL(remote string) string

NormalizeRemoteURL converts a git remote URL to a canonical host/path repository identifier:

https://github.com/x/y.git       → github.com/x/y
git@github.com:x/y.git           → github.com/x/y
ssh://git@github.com/x/y         → github.com/x/y
ssh://git@github.com:2222/x/y    → github.com/x/y

An empty input returns "".

Types

type AIAttribution added in v0.6.0

type AIAttribution struct {
	IsAIAuthored bool        `json:"isAiAuthored"`
	Tools        []string    `json:"tools,omitempty"`
	Models       []AIModel   `json:"models,omitempty"`
	HumanAuthors []Signature `json:"humanAuthors,omitempty"`
}

AIAttribution holds the result of analyzing a commit for AI authorship.

func AnalyzeAuthorship added in v0.6.0

func AnalyzeAuthorship(c Commit) AIAttribution

AnalyzeAuthorship examines a commit's co-author trailers and returns a complete attribution breakdown: which AI tools contributed, what models were used, and which human co-authors were present. AI tools and models are recognized via the canonical registry in DefaultAITools.

Examples:

"Claude Sonnet 5 <noreply@anthropic.com>"  → Tools: ["Claude Code"], Models: [{Provider: "anthropic", Model: "Sonnet 5"}]
"Claude Code <noreply@anthropic.com>"      → Tools: ["Claude Code"], Models: nil (no version in name)
"github-actions[bot] <noreply@github.com>" → Tools: ["GitHub Copilot"], Models: nil

type AICoAuthor added in v0.7.0

type AICoAuthor struct {
	Signature Signature `json:"signature"`
	Tool      string    `json:"tool"`               // e.g., "Claude Code", "GitHub Copilot", "Gemini CLI"
	Provider  string    `json:"provider,omitempty"` // canonical provider slug, e.g. "anthropic"
	Model     string    `json:"model,omitempty"`    // e.g., "Sonnet 4", "Opus 4", "gemini-2.5-pro"
}

AICoAuthor represents an AI coding assistant identified from a co-author trailer.

func MatchAITool added in v0.7.0

func MatchAITool(sig Signature, tools []AIToolPattern) *AICoAuthor

MatchAITool checks if the email matches a known AI tool and extracts the model from the name if a pattern is defined.

type AIModel added in v0.6.0

type AIModel struct {
	Provider string `json:"provider"`
	Model    string `json:"model"`
	Name     string `json:"name"`
}

AIModel holds a parsed AI model identity from a co-author trailer.

type AIStats added in v0.7.0

type AIStats struct {
	TotalCommits    int                   `json:"totalCommits"`
	AIAssistedCount int                   `json:"aiAssistedCount"`
	AIAssistedPct   float64               `json:"aiAssistedPct"`
	ByTool          map[string]ToolStats  `json:"byTool,omitempty"`
	ByModel         map[string]ModelStats `json:"byModel,omitempty"`
}

AIStats holds AI-assisted commit statistics.

type AIToolPattern added in v0.7.0

type AIToolPattern struct {
	Name         string   // Display name, e.g., "Claude Code"
	Provider     string   // Canonical provider slug, e.g. "anthropic"
	Emails       []string // Known email addresses (lowercase)
	ModelPattern *regexp.Regexp
}

AIToolPattern defines how to recognize an AI tool from co-author email and extract model version from the name.

type CategoryStats added in v0.7.0

type CategoryStats struct {
	Category   string `json:"category"`
	Commits    int    `json:"commits"`
	Insertions int    `json:"insertions"`
	Deletions  int    `json:"deletions"`
}

CategoryStats holds commit and LOC counts for one conventional-commit category.

func (CategoryStats) NetAdditions added in v0.7.0

func (s CategoryStats) NetAdditions() int

NetAdditions returns insertions minus deletions.

type Commit

type Commit struct {
	Hash         string    `json:"hash"`
	Author       Signature `json:"author"`
	AuthorDate   time.Time `json:"authorDate"`
	Committer    Signature `json:"committer"`
	CommitDate   time.Time `json:"commitDate"`
	Subject      string    `json:"subject"`
	Trailers     []Trailer `json:"trailers,omitempty"`
	Insertions   int       `json:"insertions"`
	Deletions    int       `json:"deletions"`
	FilesChanged int       `json:"filesChanged"`
}

Commit is one parsed log entry. Bodies are not extracted — subjects and trailers cover attribution and classification needs; consumers that need full messages can run git directly.

func (Commit) AICoAuthors added in v0.7.0

func (c Commit) AICoAuthors() []AICoAuthor

AICoAuthors returns AI coding assistants identified from co-author trailers. Uses DefaultAITools for recognition.

func (Commit) AICoAuthorsWithTools added in v0.7.0

func (c Commit) AICoAuthorsWithTools(tools []AIToolPattern) []AICoAuthor

AICoAuthorsWithTools returns AI coding assistants using a custom tool registry.

func (Commit) CoAuthors

func (c Commit) CoAuthors() []Signature

CoAuthors returns identities from Co-authored-by trailers.

func (Commit) IsAIAssisted added in v0.7.0

func (c Commit) IsAIAssisted() bool

IsAIAssisted returns true if any co-author is a recognized AI tool.

func (Commit) ParseConventional added in v0.6.0

func (c Commit) ParseConventional() *ConventionalCommit

ParseConventional parses this commit's subject as a conventional commit. Returns nil if the subject does not match.

func (Commit) TrailerValue added in v0.6.0

func (c Commit) TrailerValue(key string) string

TrailerValue returns the first trailer value matching the given key (case-insensitive), or "" if not found.

func (Commit) TrailerValues added in v0.6.0

func (c Commit) TrailerValues(key string) []string

TrailerValues returns all trailer values matching the given key (case-insensitive).

type CommitStatsOptions added in v0.7.0

type CommitStatsOptions struct {
	// Since and Until bound the commit date range (inclusive).
	Since time.Time
	Until time.Time
	// NoMerges excludes merge commits from statistics.
	NoMerges bool
	// Author filters commits by author (git regex).
	Author string
}

CommitStatsOptions configures commit statistics collection.

type ConventionalCommit added in v0.6.0

type ConventionalCommit struct {
	Type     string `json:"type"`
	Scope    string `json:"scope,omitempty"`
	Breaking bool   `json:"breaking"`
	Subject  string `json:"subject"`
}

ConventionalCommit holds parsed conventional commit components.

func ParseConventionalCommit added in v0.6.0

func ParseConventionalCommit(subject string) *ConventionalCommit

ParseConventionalCommit parses the subject line of a conventional commit. Returns nil if the subject does not match the pattern.

type LogOptions

type LogOptions struct {
	// Since and Until bound the commit date (half-open in practice: git
	// treats both bounds inclusively at second resolution).
	Since time.Time
	Until time.Time
	// SinceCommit limits output to commits reachable from HEAD but not
	// from the given commit SHA (i.e., "sha..HEAD"). Used for incremental
	// ingestion with high-water marks.
	SinceCommit string
	// Rev logs commits reachable from this revision (e.g. "origin/main" or
	// "@{upstream}") instead of the default HEAD. Ignored when SinceCommit
	// is set, which already pins the range to HEAD.
	Rev string
	// Author filters by author name or email (git regex semantics).
	Author string
	// NoMerges excludes merge commits.
	NoMerges bool
	// MaxCount caps the number of commits returned (0 = unlimited).
	MaxCount int
	// IncludeStats adds per-commit insertions/deletions/files-changed via
	// --numstat. Costs proportionally more; leave false when not needed.
	IncludeStats bool
	// Reverse returns commits in chronological order (oldest first)
	// instead of the default newest-first.
	Reverse bool
}

LogOptions filters a commit-log query. Zero values leave a filter unset.

type ModelStats added in v0.7.0

type ModelStats struct {
	Tool       string `json:"tool"`
	Model      string `json:"model"`
	Commits    int    `json:"commits"`
	Insertions int    `json:"insertions"`
	Deletions  int    `json:"deletions"`
}

ModelStats holds per-model commit counts (tool + model).

type MultiRepoCommitStats added in v0.7.0

type MultiRepoCommitStats struct {
	Since      time.Time                `json:"since"`
	Until      time.Time                `json:"until"`
	TotalStats CategoryStats            `json:"total"`
	ByCategory map[string]CategoryStats `json:"byCategory"`
	AIStats    AIStats                  `json:"aiStats"`
	ByRepo     []RepoCommitStats        `json:"byRepo"`
	Errors     []RepoError              `json:"errors,omitempty"`
}

MultiRepoCommitStats is the aggregated commit/LOC breakdown across multiple repositories.

func AggregateCommitStats added in v0.7.0

func AggregateCommitStats(ctx context.Context, paths []string, opts CommitStatsOptions, workers int) *MultiRepoCommitStats

AggregateCommitStats collects commit statistics across multiple repositories in parallel. Repositories that fail are recorded in Errors but don't stop the aggregation. Workers controls concurrency; 0 defaults to GOMAXPROCS.

func (*MultiRepoCommitStats) CategoryBreakdown added in v0.7.0

func (m *MultiRepoCommitStats) CategoryBreakdown() []CategoryStats

CategoryBreakdown returns categories sorted by commit count (descending).

func (*MultiRepoCommitStats) CategoryPercentages added in v0.7.0

func (m *MultiRepoCommitStats) CategoryPercentages() map[string]float64

CategoryPercentages returns categories with their percentage of total commits.

type PendingResult added in v0.10.0

type PendingResult struct {
	// Commits are the pending commits, oldest first.
	Commits []Commit
	// Baseline is the ref the commits were computed as being ahead of: the
	// configured upstream ("@{upstream}"), a remote-tracking branch (e.g.
	// "origin/main"), or an explicit since-commit hash. It is empty when the
	// branch has no push baseline at all — never pushed, or no upstream — in
	// which case every commit reachable from HEAD is pending.
	Baseline string
}

PendingResult is the outcome of a PendingCommits query.

type ProgressFunc added in v0.6.0

type ProgressFunc func(completed, total int, path string)

ProgressFunc is called during parallel operations with progress updates.

type PushedResult added in v0.10.0

type PushedResult struct {
	// Commits are the pushed commits, most recent first.
	Commits []Commit
	// Baseline is the ref the commits were read from: the configured
	// upstream ("@{upstream}") or the matching remote-tracking branch (e.g.
	// "origin/main"). It is empty when the branch has no push target, in
	// which case nothing has been pushed and Commits is empty.
	Baseline string
}

PushedResult is the outcome of a PushedCommits query.

type Repo

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

Repo is a handle to a local git repository.

func Open

func Open(path string) (*Repo, error)

Open returns a Repo for path, verifying it is a git repository.

func (*Repo) Branch

func (r *Repo) Branch(ctx context.Context) (string, error)

Branch returns the current branch name, or "HEAD" when detached.

func (*Repo) CollectCommitStats added in v0.7.0

func (r *Repo) CollectCommitStats(ctx context.Context, opts CommitStatsOptions) (*RepoCommitStats, error)

CollectCommitStats aggregates commit counts and LOC by conventional-commit category for a single repository over the given time range. Non-conventional commits are grouped under "uncategorized". Returns an error if the repo cannot be read; an empty repo returns zero stats, not an error.

func (*Repo) HasUpstream added in v0.9.0

func (r *Repo) HasUpstream(ctx context.Context) (bool, error)

HasUpstream reports whether the repository's current branch has an upstream (remote-tracking) branch configured that resolves to a commit. A detached HEAD, a branch with no upstream, or a configured-but-missing upstream ref all return (false, nil) rather than an error.

func (*Repo) Log

func (r *Repo) Log(ctx context.Context, opts LogOptions) ([]Commit, error)

Log returns commits matching opts, newest first (git log order) unless Reverse is set.

func (*Repo) OriginURL

func (r *Repo) OriginURL(ctx context.Context) (string, error)

OriginURL returns the raw URL of the "origin" remote, or "" when no origin is configured.

func (*Repo) Path

func (r *Repo) Path() string

Path returns the repository's working-tree path.

func (*Repo) PendingCommits added in v0.9.0

func (r *Repo) PendingCommits(ctx context.Context, sinceCommit string) (PendingResult, error)

PendingCommits returns commits that exist locally but have not yet been pushed, oldest first, along with the baseline they were computed against.

By default the baseline is the branch's push target: its configured upstream, or failing that the matching remote-tracking branch (e.g. origin/main). When the branch has no such baseline — it was never pushed, or has no upstream configured — every commit reachable from HEAD is treated as pending and Baseline is empty. This mirrors gitscan's scan semantics, where a real branch with no upstream counts as having unpushed work. A detached HEAD has no branch to push and yields no baseline.

If sinceCommit is non-empty it overrides the baseline entirely ("sinceCommit..HEAD"), regardless of any upstream.

func (*Repo) PushedCommits added in v0.10.0

func (r *Repo) PushedCommits(ctx context.Context, limit int) (PushedResult, error)

PushedCommits returns up to limit commits that have already been pushed on the current branch — those reachable from its push target (the configured upstream, or failing that the matching remote-tracking branch such as origin/main) — most recent first. A limit of zero or less returns all pushed commits.

When the branch has no push target (never pushed, no upstream), nothing is considered pushed: Commits is empty and Baseline is "". This is the mirror image of PendingCommits, which reports every commit as pending in the same situation.

func (*Repo) Tags

func (r *Repo) Tags(ctx context.Context) ([]string, error)

Tags returns the repository's tag names, or an empty slice when there are none.

func (*Repo) TagsWithDates

func (r *Repo) TagsWithDates(ctx context.Context) (map[string]time.Time, error)

TagsWithDates returns tag names mapped to their creation dates (the tag object date for annotated tags, the commit date for lightweight tags).

type RepoCommitStats added in v0.7.0

type RepoCommitStats struct {
	Path       string                   `json:"path"`
	Since      time.Time                `json:"since"`
	Until      time.Time                `json:"until"`
	TotalStats CategoryStats            `json:"total"`
	ByCategory map[string]CategoryStats `json:"byCategory"`
	AIStats    AIStats                  `json:"aiStats"`
}

RepoCommitStats is the commit/LOC breakdown for a single repository over a time range.

type RepoError added in v0.7.0

type RepoError struct {
	Path  string `json:"path"`
	Error string `json:"error"`
}

RepoError records a repository that failed during collection.

type RepoResult added in v0.6.0

type RepoResult[T any] struct {
	Path  string
	Value T
	Err   error
}

RepoResult holds the outcome of a parallel operation on one repository.

func RunAll added in v0.6.0

func RunAll[T any](ctx context.Context, paths []string, fn func(ctx context.Context, repo *Repo) (T, error), workers int) []RepoResult[T]

RunAll executes fn on each repository path in parallel, returning results in the same order as paths. Workers controls concurrency; 0 defaults to GOMAXPROCS. If ctx is cancelled, in-flight operations finish but queued ones are skipped.

func RunAllPaths added in v0.6.0

func RunAllPaths[T any](ctx context.Context, paths []string, fn func(ctx context.Context, path string) (T, error), workers int) []RepoResult[T]

RunAllPaths executes fn on each path in parallel without requiring a git repository. This is the lower-level variant for operations that manage their own Repo lifecycle or work on non-repo directories.

func RunAllWithProgress added in v0.6.0

func RunAllWithProgress[T any](ctx context.Context, paths []string, fn func(ctx context.Context, repo *Repo) (T, error), workers int, progress ProgressFunc) []RepoResult[T]

RunAllWithProgress is like RunAll but reports progress via a callback.

type Signature

type Signature struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

Signature is an author or committer identity.

type ToolStats added in v0.7.0

type ToolStats struct {
	Tool       string `json:"tool"`
	Commits    int    `json:"commits"`
	Insertions int    `json:"insertions"`
	Deletions  int    `json:"deletions"`
}

ToolStats holds per-tool commit counts.

type Trailer

type Trailer struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

Trailer is one commit-message trailer line, e.g. "Co-authored-by: Jane <jane@example.com>".

Directories

Path Synopsis
cmd
gitscan command
internal
cliutil
Package cliutil provides small CLI-input-normalization helpers shared across gitscan's subcommands.
Package cliutil provides small CLI-input-normalization helpers shared across gitscan's subcommands.
render
Package render formats gitscan command results for output.
Package render formats gitscan command results for output.

Jump to

Keyboard shortcuts

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