gogit

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 13 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 KnownAIProviders Claude Code, Copilot, Gemini CLI, Cursor, Aider
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

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 gitscan
go build -o gitscan .

Usage

gitscan <directory>              # Scan for issues
gitscan since <duration> [dir]   # Filter by modification time
gitscan dep <module> [dir]       # Filter by dependency
gitscan order [dir]              # Show repos in dependency order
Root Command (Issue Scanning)

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

gitscan ~/go/src/github.com/grokify
Flag Short Default Description
--dir -d (required) Directory containing repos to scan
--format -f list Output format: list or table
--show-clean false Show repos with no issues
--summary true Show summary at the end
--go-git false Use go-git library instead of git CLI
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)
--recurse -r false Check nested go.mod files
--go-git false Use go-git library instead of git CLI

Duration formats: 7d (days), 2w (weeks), 1m (months), 24h (hours)

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
--go-git false Use go-git library instead of git CLI
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

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]
Flag Short Default Description
--dir -d (required) Directory containing repos to scan
--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
--go-git false Use go-git library instead of git CLI
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.

Git Backend Options

By default, gitscan uses the git CLI for repository status checks, which is fast and compatible with all git configurations. An optional --go-git flag enables the go-git library backend (pure Go, no process spawning):

Backend Speed Compatibility
git CLI (default) Fast (~2.5s for 600 repos) Full compatibility
go-git (--go-git) Slower (~10s for 600 repos) Pure Go, no external deps

The git CLI backend is recommended for most use cases. Use --go-git in environments where the git binary is unavailable.

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 KnownAIProviders = []AIProvider{
	{
		Name:     "Claude Code",
		Provider: "anthropic",
		Emails:   []string{"noreply@anthropic.com"},
	},
	{
		Name:     "GitHub Copilot",
		Provider: "github",
		Emails:   []string{"noreply@github.com", "copilot@github.com"},
	},
	{
		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",
		},
	},
	{
		Name:     "Cursor",
		Provider: "cursor",
		Emails:   []string{"ai@cursor.sh", "cursor@cursor.sh"},
	},
	{
		Name:     "Aider",
		Provider: "aider",
		Emails:   []string{"aider@aider.chat"},
	},
}

KnownAIProviders lists recognized AI coding assistants and their co-author signatures. This is the canonical registry; consumers should use AIProviderByEmail rather than maintaining separate lists.

Functions

func AIToolByEmail added in v0.6.0

func AIToolByEmail(email string) string

AIToolByEmail returns the tool name matching a co-author email, or "" when the email is not a known AI signature. This is a convenience wrapper around AIProviderByEmail for callers that only need the name.

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

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.

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.

func ParseAIModel added in v0.6.0

func ParseAIModel(name, email string) *AIModel

ParseAIModel extracts the AI model from a co-author signature's name and email. Returns nil if the signature is not from a known AI provider or the model cannot be determined.

Examples:

"Claude Sonnet 5 <noreply@anthropic.com>"  → {Provider: "anthropic", Model: "sonnet-5", Name: "Claude Sonnet 5"}
"Claude Opus 4.6 <noreply@anthropic.com>"  → {Provider: "anthropic", Model: "opus-4.6", Name: "Claude Opus 4.6"}
"github-actions[bot] <noreply@github.com>" → {Provider: "github", Model: "", Name: "github-actions[bot]"}

type AIProvider added in v0.6.0

type AIProvider struct {
	// Name is the display name, e.g. "Claude Code".
	Name string
	// Provider is the canonical provider slug, e.g. "anthropic".
	Provider string
	// Emails are known co-author addresses. Entries of the form
	// "<id>+<slug>@users.noreply.github.com" also match any address with
	// the same "+<slug>@users.noreply.github.com" suffix.
	Emails []string
}

AIProvider identifies an AI coding assistant provider.

func AIProviderByEmail added in v0.6.0

func AIProviderByEmail(email string) *AIProvider

AIProviderByEmail returns the AI provider matching a co-author email, or nil when the email is not a known AI signature. Matching is case-insensitive and supports GitHub noreply suffix forms with variable user-ID prefixes.

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

func (c Commit) CoAuthors() []Signature

CoAuthors returns identities from Co-authored-by trailers.

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 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
	// 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 ProgressFunc added in v0.6.0

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

ProgressFunc is called during parallel operations with progress updates.

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

Jump to

Keyboard shortcuts

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