gogit
Generic, dependency-light Git ergonomics for Go, by shelling out to the
git CLI. 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.
Library
- Discovery —
Discover(roots, maxDepth)finds repositories under directory roots without descending into them. - Commit log —
Repo.Logparses commits with calendar-date filtering, author/committer identities, subjects, trailers (Commit.CoAuthors()forCo-authored-by),--numstatchange stats, and an optional message body (LogOptions.IncludeBody, opt-in like stats since it costs more per commit). - Incremental ingestion —
LogOptions.SinceCommitlimits output to commits after a given SHA (sha..HEAD) for high-water-mark workflows;LogOptions.Revlogs from an arbitrary revision instead of HEAD. - Reverse order —
LogOptions.Reversereturns commits oldest-first. - AI authorship —
AnalyzeAuthorshipdetects AI coding assistants (Claude Code, GitHub Copilot, Gemini CLI, Cursor, Aider) fromCo-authored-bytrailers, extracting tool names and model identities. - AI co-author parsing —
Commit.AICoAuthors()identifies AI tools from co-author trailers with model version extraction (e.g., "Sonnet 4"). - Commit stats by category —
Repo.CollectCommitStatsandAggregateCommitStatsaggregate commit counts and LOC by conventional- commit type, with AI-assisted metrics per tool and model. - Conventional commits —
ParseConventionalCommitextracts type, scope, breaking flag, and subject from conventional commit messages. - Trailer lookup —
Commit.TrailerValueandTrailerValuesprovide case-insensitive access to any trailer key. - Parallel execution —
RunAllandRunAllWithProgressexecute operations across multiple repositories concurrently with configurable worker count and context cancellation. - Metadata —
Repo.Branch,Repo.OriginURL,NormalizeRemoteURL(canonicalhost/pathidentifiers),Repo.Tags,Repo.TagsWithDates. - Pending & pushed commits —
Repo.PendingCommitslists commits ahead of the branch's push target: its upstream, the matching remote-tracking branch (e.g.origin/main), or — for a branch never pushed under its own name — the remote's default branch, so only commits unique to the branch are reported. Only when none of these resolve is every commit reachable fromHEADreported as pending.Repo.PushedCommitslists the most recent commits already pushed (unaffected by the default-branch fallback, since it reports what this branch itself has pushed). Both results carry the current branch name (Branch), so a caller reporting "no upstream" also knows which branch that refers to.Repo.HasUpstreamreports whether an upstream is configured. - File & content access —
Repo.LsFiles(tracked and, optionally, untracked files),Repo.StagedFiles(staged additions/modifications),Repo.ShowContent(object content at a git spec, e.g.:pathfor the staged version),Repo.LsTree(a revision's full file list), andRepo.LastCommitTouching(the most recent commit at or before a revision that modified a path). - Exposure — answer how far a commit or file has travelled.
Repo.RefsContaininglists the branches, remote-tracking branches, and tags containing a commit (symbolic refs such asorigin/HEADomitted);Repo.TagsWithPathlists the tags whose tree contains a path, separating content that only lives in history from content that shipped in a release;Repo.FilesEverAddedlists every path ever added on any ref, including files since deleted fromHEAD; andRepo.IgnoredFileslists untracked paths excluded by ignore rules.
Exposure Queries
Once a repository scan turns something up — a credential, a file that
should never have been committed — the next question is what that finding
reaches. Four Repo methods answer it without ad-hoc git commands:
| Question | Method | Git equivalent |
|---|---|---|
| Is this commit pushed? Released? | RefsContaining(ctx, commit) |
git for-each-ref --contains |
| Did this path ship in a tagged release? | TagsWithPath(ctx, path) |
git cat-file --batch-check over <tag>:<path> |
| What has ever been committed, including since-deleted files? | FilesEverAdded(ctx) |
git log --all --diff-filter=A |
| What exists locally but is ignored? | IgnoredFiles(ctx) |
git ls-files --others --ignored --directory |
repo, _ := gogit.Open("/path/to/repo")
// Where has the commit that introduced a file gone?
refs, _ := repo.RefsContaining(ctx, "a1b2c3d")
for _, ref := range refs {
fmt.Println(ref.Kind, ref.Name) // branch main, remote origin/main, tag v1.2.0
}
// Was the file itself part of any tagged release tree?
tags, _ := repo.TagsWithPath(ctx, "config/local.env")
fmt.Println(len(tags) > 0)
RefsContaining and TagsWithPath answer different questions: a tag cut
after the commit that added a file is downstream of it, but does not
contain the file if a later commit deleted it before tagging. For Go
modules the tagged tree is what proxy.golang.org archives permanently, so
TagsWithPath is the check that tells "only in history" apart from
"published in a release."
Notes:
RefsContainingomits symbolic refs (origin/HEAD) and returns an error for an unknown commit, so a typo never reads as "not pushed." Remote refs reflect the last fetch.TagsWithPathaccepts files or directories relative to the repository root, and checks every tag in one git process.FilesEverAddeddisables rename detection, so a renamed file appears under both names. Files introduced only by a merge commit's conflict resolution are not reported, matchinggit log's default.IgnoredFilescollapses fully ignored directories to a singledir/entry and honors the global excludes file.
gitgrep
A policy-free primitive for searching a repository's content and history,
by shelling out to native git. Callers supply patterns and get matches back;
gitgrep has no built-in notion of what a match means, so it is a
general-purpose building block rather than a leak/secret scanner itself.
GrepTree— search the working tree, the index (Staged), or a revision (Rev) for one or more patterns (fixed-string or extended regex, case-insensitive optional).HistoryPickaxe— find commits whose diff added or removed a pattern (git log -S/-G), attributed to the specific file.StreamPatches— streamgit log -pfile diffs with commit/author/ date context, for running custom detectors over a single git walk.
matches, _ := gitgrep.GrepTree(ctx, repoPath, gitgrep.Options{
Patterns: []gitgrep.Pattern{{Value: "Acme Corp", IgnoreCase: true}},
})
Match.Text and Patch.Hunk are returned verbatim; redacting them before
display or logging is the caller's responsibility. See the
gitgrep design note
for the full API and caveats.
repo, _ := gogit.Open("/path/to/repo")
commits, _ := repo.Log(ctx, gogit.LogOptions{
Since: weekStart,
IncludeStats: true,
})
for _, c := range commits {
attr := gogit.AnalyzeAuthorship(c)
fmt.Println(c.Hash, attr.Tools, c.Insertions)
}
Install:
go get github.com/grokify/gogit
gitscan CLI
Scan many repositories for ones needing attention: uncommitted or
unpushed changes, replace directives, module/directory mismatches,
dependency filters, release ordering, and workflow compliance. The
pending subcommand reports unpushed commits across one or many
repositories (sweep several org directories at once), and pushed
lists the most recent commits already pushed — both as an aligned
terminal table, copy-pasteable markdown, or JSON for agents. grep
searches a single repository's working tree, staged index, a revision,
or history for one or more patterns.
go install github.com/grokify/gogit/cmd/gitscan@latest
See the README for full CLI usage, and Releases for version history.