Help us shape the future of Go! Take the 2026 Go Developer Survey

commitmap

command module
v0.0.0-...-ddc50a6 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 2 Imported by: 0

README

commitmap

Turn git history into a map of where work actually happened.

commitmap SVG output for a small example repository

Why

git log tells you what happened, one commit at a time. It does not tell you which parts of a codebase soak up the effort, which branches carried the work, or who works where. commitmap reads the history once and folds it into a tree you can scan in a few seconds: by directory, by Conventional Commits scope, by merged branch, or by author. It shells out to your own git, so it sees exactly what git log sees, including .mailmap.

Install

go install github.com/Mr-hunt-007/commitmap@latest

go install puts the binary in $(go env GOPATH)/bin (usually ~/go/bin). If your shell says command not found, add that directory to your PATH:

echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc && source ~/.zshrc   # bash: ~/.bashrc

On Windows the Go installer adds %USERPROFILE%\go\bin to PATH for you.

Or build from source:

git clone https://github.com/Mr-hunt-007/commitmap
cd commitmap
go build -o commitmap .

Requires git 2.31 or newer on PATH. No other dependencies.

Usage

commitmap [flags] [repo]

repo defaults to the current directory. All examples below are real runs against a small example repository (44 non-merge commits, 12 merges).

By directory (default)
$ commitmap --top 4
main  (44 commits, 4 authors, 2024-01-01 → 2024-05-21)
│
├── auth         15 commits  2 authors  last 2y
│   ├── login    12 commits  1 author   last 2y
│   └── oauth     3 commits  1 author   last 2y
├── dashboard    12 commits  2 authors  last 2y
│   ├── styles    6 commits  1 author   last 2y
│   └── widgets   6 commits  1 author   last 2y
├── payments     10 commits  1 author   last 2y
│   └── stripe    6 commits  1 author   last 2y
├── docs          6 commits  1 author   last 2y
└── … 1 more hidden by --min/--top

skipped 4 changes to vendored, build or lock files (--include-all counts them)

A commit counts once per directory no matter how many files it touches there. payments has 10 commits while payments/stripe has 6 because four commits only touched files directly inside payments/. Files at the top of the repository are grouped under (root). A rename counts for both the old and the new directory.

By Conventional Commits scope
$ commitmap --by scope
main  (44 commits, 4 authors, 2024-01-01 → 2024-05-21)
│
├── auth        15 commits  2 authors  last 2y  feat 9, fix 6
│   ├── login   12 commits  1 author   last 2y  feat 6, fix 6
│   └── oauth    3 commits  1 author   last 2y  feat 3
├── dashboard   12 commits  2 authors  last 2y  feat 6, style 6
├── payments    10 commits  1 author   last 2y  feat 6, fix 4
│   └── stripe   6 commits  1 author   last 2y  feat 6
└── (unscoped)   7 commits  2 authors  last 2y  other 6, chore 1

types: feat 21, fix 10, other 6, style 6, chore 1

feat(auth/login): ... and fix(auth:login): ... both land under auth then login. chore(api,web): ... counts for both scopes. Scopes are lower-cased so Auth and auth merge. Commits with no scope go under (unscoped); commits that are not Conventional Commits at all show as type other.

By merged branch
$ commitmap --by branch
main  (44 commits, 4 authors, 2024-01-01 → 2024-05-21)
│
├── (direct)         19 commits  2 authors  last 2y
├── feature          15 commits  2 authors  last 2y
│   └── auth         15 commits  2 authors  last 2y
│       ├── login-1   3 commits  2 authors  last 2y
│       ├── login-2   3 commits  2 authors  last 2y
│       ├── login-3   3 commits  2 authors  last 2y
│       ├── login-4   2 commits  1 author   last 2y
│       ├── login-5   2 commits  1 author   last 2y
│       └── login-6   2 commits  1 author   last 2y
└── fix              10 commits  1 author   last 2y
    ├── payments-1    2 commits  1 author   last 2y
    ├── payments-2    2 commits  1 author   last 2y
    ├── payments-3    2 commits  1 author   last 2y
    ├── payments-4    2 commits  1 author   last 2y
    ├── payments-5    1 commit   1 author   last 2y
    └── payments-6    1 commit   1 author   last 2y

For every merge that landed on --rev (the merges on its first-parent chain), commitmap reads the branch name from the subject and counts the commits that merge brought in, the same set as git rev-list A^1..A^2. Commits committed straight onto the branch go under (direct). Because each commit enters the first-parent chain exactly once, top-level counts add up to the total and nothing is counted twice. Back-merges such as Merge branch 'main' into feature/x inside a feature branch do not create a fake main node.

Recognised subjects:

Subject Branch
Merge branch 'feature/x' (optionally into main or of <url>) feature/x
Merge branches 'a', 'b' and 'c' (octopus) a, b, c
Merge pull request #12 from owner/feature/x feature/x
Merge remote-tracking branch 'origin/feature/x' feature/x
Merged in feature/x (pull request #3) feature/x
Merge feature/x into main feature/x

Anything else is counted under (unparsed merges) and reported in a footer.

By author
$ commitmap --by author --min 5
main  (44 commits, 4 authors, 2024-01-01 → 2024-05-21)
│
├── Ines Park      13 commits  1 author   last 2y
│   ├── payments   10 commits  1 author   last 2y
│   └── … 1 more hidden by --min/--top
├── Leo Grant      12 commits  1 author   last 2y
│   ├── dashboard   6 commits  1 author   last 2y
│   └── docs        6 commits  1 author   last 2y
├── Sam Ortiz      12 commits  1 author   last 2y
│   └── auth       12 commits  1 author   last 2y
└── Maya Chen       7 commits  1 author   last 2y
    ├── dashboard   6 commits  1 author   last 2y
    └── … 1 more hidden by --min/--top

skipped 4 changes to vendored, build or lock files (--include-all counts them)

Authors are identified by email after .mailmap is applied (%aN/%aE), case-insensitively. If one name appears with several emails that .mailmap does not join, each gets its own node labelled Name <email>.

More examples
commitmap --since "6 months ago" --top 8        # recent hot spots
commitmap --path services/api --depth 3         # zoom into one area
commitmap --rev v1.4.0..v1.5.0 --by scope       # what went into a release
commitmap --by branch --rev origin/main --depth 1
commitmap --svg                                 # writes <repo>-commitmap.svg
commitmap ~/code/api --svg map.svg --html map.html
commitmap --json | jq '.root.children[] | {path, commits}'

Use with AI agents

The CLI is already agent-friendly: --json has a stable, documented shape and the exit codes below are stable. For agents that speak the Model Context Protocol, commitmap --mcp runs an MCP server on stdio.

The client starts commitmap itself, so it must be on the PATH the client sees. Editors launched from a dock or start menu often do not inherit your shell's PATH; if the server fails to start, use the absolute path of the binary instead (print it with echo "$(go env GOPATH)/bin/commitmap").

Claude Code (add --scope user to enable it in every project):

claude mcp add commitmap -- commitmap --mcp

Codex CLI:

codex mcp add commitmap -- commitmap --mcp

or in ~/.codex/config.toml:

[mcp_servers.commitmap]
command = "commitmap"
args = ["--mcp"]

Cursor, in .cursor/mcp.json (or ~/.cursor/mcp.json for every project):

{"mcpServers": {"commitmap": {"command": "commitmap", "args": ["--mcp"]}}}

VS Code, in .vscode/mcp.json:

{"servers": {"commitmap": {"type": "stdio", "command": "commitmap", "args": ["--mcp"]}}}

Gemini CLI, in ~/.gemini/settings.json (or .gemini/settings.json in a project):

{"mcpServers": {"commitmap": {"command": "commitmap", "args": ["--mcp"]}}}

Tools:

Tool Kind What it answers
commitmap_map read-only Where work has happened, who works where, what went into a release. Takes the same filters as the CLI (dir, by, depth, rev, since, until, author, paths, min, top, merges, include_all) and returns the same JSON as --json. top defaults to 10 to keep the answer small; a node's hidden count says what was left out, and top: 0 returns everything.
commitmap_render writes files Writes the SVG and/or HTML version (svg, html, optional svg_path, html_path) and returns the absolute paths. Without a path the files are <repo>-commitmap.svg/.html in the server's working directory. It creates new files and only replaces files commitmap wrote before; any other existing file is refused.

Every tool is read-only or only creates its own output files, so there is nothing to unlock: --allow-destructive is accepted for consistency with related tools and changes nothing.

An Agent Skill that teaches agents when and how to run the CLI is in skills/commitmap. Install it for Claude Code or Codex:

mkdir -p ~/.claude/skills && cp -r skills/commitmap ~/.claude/skills/
mkdir -p ~/.agents/skills && cp -r skills/commitmap ~/.agents/skills/

Contributors and agents working on this repository: see AGENTS.md.

Flags

Flag Default Meaning
--by MODE path path, scope, branch or author
--depth N path 2, branch 3, author 1, scope 0 Tree depth. 0 means unlimited. In author mode it is the directory depth under each author.
--rev REV HEAD Revision or range, for example main or v1.0..v2.0
--since DATE Passed to git log --since
--until DATE Passed to git log --until
--author PATTERN Passed to git log --author
--path PATH Only commits touching PATH (relative to the repo). Repeatable.
--min N 0 Hide nodes with fewer than N commits
--top N 0 Keep only the N largest children of each node
--merges off Count merge commits too. Their file changes are taken against the first parent.
--include-all off Do not skip vendored, build and lock files
--json off Print JSON instead of the tree
--svg [FILE] Also write an SVG diagram. Without FILE: <repo>-commitmap.svg in the current directory
--html [FILE] Also write a single self-contained HTML page. Without FILE: <repo>-commitmap.html
--no-color off No ANSI colour. NO_COLOR is honoured too. Colour is only used when stdout is a terminal.
--mcp off Run as an MCP server on stdio (see Use with AI agents). Other flags are ignored.
--allow-destructive off Only valid with --mcp. commitmap has no destructive tools, so it changes nothing.
--version Print the version
-h, --help Show help

Flags may appear before or after the repository path. --svg and --html overwrite the named file if it exists. They take the next argument as the file name only when it ends in .svg (or .html/.htm), so commitmap --svg ~/code/api maps ~/code/api and saves api-commitmap.svg. Use --svg=FILE for any other name.

Skipped paths

Unless --include-all is given, file changes under these directories are ignored: node_modules, vendor, dist, bower_components, __pycache__, .next, .nuxt, .svelte-kit. So are these files wherever they appear: package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock, bun.lockb, deno.lock, go.sum, go.work.sum, Cargo.lock, Gemfile.lock, composer.lock, poetry.lock, Pipfile.lock, uv.lock, pdm.lock, mix.lock, pubspec.lock, Package.resolved, flake.lock, packages.lock.json, gradle.lockfile, plus *.min.js and *.min.css. The number of skipped changes is printed below the tree. A commit whose only changes were skipped still counts in the total at the top.

SVG and HTML

commitmap --svg --html      # <repo>-commitmap.svg and <repo>-commitmap.html

<repo> is the name of the repository's top-level directory, even when you run commitmap from a subdirectory. Each file written is reported on stderr, so the tree on stdout (or --json) is unchanged.

--svg writes a horizontal tree: one row per leaf, a bar per node whose length is proportional to its commit count, colours per top-level group. It uses a monospace font so label widths are predictable, shortens names longer than 40 characters (the full path is in the hover title), and follows the viewer's light or dark scheme.

--html writes one file with the same diagram inline, plus a collapsible tree with author counts, last-commit dates and type chips in scope mode. It has no external requests (no CDN, no fonts, no scripts from elsewhere), so it works offline and can be attached to a ticket or mailed around.

JSON

--json prints one object. Shape (from a real run with --depth 1 --top 2, children trimmed):

{
  "version": "0.1.0",
  "mode": "path",
  "rev": "HEAD",
  "label": "main",
  "depth": 1,
  "filters": {
    "top": 2,
    "merges": false,
    "include_all": false
  },
  "ignored_changes": 4,
  "root": {
    "name": "main",
    "path": "",
    "commits": 44,
    "authors": 4,
    "first": "2024-01-01T10:00:00Z",
    "last": "2024-05-21T10:00:00Z",
    "hidden": 3,
    "children": [
      {
        "name": "auth",
        "path": "auth",
        "commits": 15,
        "authors": 2,
        "first": "2024-01-04T10:00:00Z",
        "last": "2024-05-07T10:00:00Z",
        "hidden": 0,
        "children": []
      }
    ]
  }
}
Field Notes
version, mode, rev, label, depth label is the branch name when rev is HEAD. depth 0 means unlimited.
filters since, until, author, paths, min, top appear only when set. merges and include_all are always present.
ignored_changes File changes skipped as vendored, build or lock files.
merges, unparsed_merges Branch mode only: merges that brought in at least one counted commit, and how many of those named no branch.
node name, path path joins names with /; empty for the root.
node key Author mode only: the identity key (lower-cased email).
node commits, authors Distinct commits and distinct authors at or below this node.
node first, last RFC 3339 UTC author dates. Omitted when there are no commits.
node types Scope mode only: commit count per Conventional Commits type.
node hidden Children removed by --min or --top.
node children Always an array, sorted by commits (descending) then name.

Fields are only added, never renamed or removed, within 0.x minor versions.

Exit codes

Code Meaning
0 Success, including an empty repository or a filter that matched nothing
1 git failed or is missing, the path is not a git repository, or an output file could not be written
2 Invalid flags or arguments

How it works

commitmap runs git log -z --format=%H%x1f%aN%x1f%aE%x1f%at%x1f%P%x1f%s --name-status -M and parses the NUL-separated stream as it arrives, so it never holds more than one commit's file list in memory. Paths with spaces, quotes, newlines or non-ASCII characters come through unquoted. Merge commits are skipped with --no-merges unless --merges is set, so work on a branch is not counted a second time when it is merged.

Branch mode loads the commit graph once with git rev-list --parents and walks the first-parent chain oldest first, which yields every merge's A^1..A^2 set in a single linear pass instead of one git rev-list per merge.

On a 37,000-commit repository with 15,000 merges, path mode takes about 6 seconds (most of it git's rename detection) with a Go heap under 10 MB. Branch mode takes under 2 seconds; it keeps the commit graph in memory, about 45 MB of heap for that repository.

Limitations

  • Counts are distinct commits, not lines changed. A one-character fix and a 10,000-line rewrite weigh the same.
  • In path, scope and author modes a commit can count under several siblings, so children do not have to add up to their parent. Branch mode is the exception: top-level nodes partition the commits.
  • Dates are author dates. --since and --until are passed to git, which filters on committer date, so a rebased commit can pass the filter while showing an older author date.
  • Branch mode only knows what merge subjects say. Squash merges and rebase merges leave no merge commit, so their commits show as (direct). A branch merged into develop and then develop into main is credited to develop when mapping main; map develop to see the feature branches.
  • --rev takes a single revision or range. Symmetric ranges (A...B) and multiple tips are not supported in branch mode.
  • Scope mode trusts commit messages. Non-standard types are accepted only when a scope is present (deps(npm): ...); a bare sorbet: ... prefix is not treated as Conventional Commits.
  • Rename detection is git's default -M similarity (50%). Very large renames may exceed git's rename limit and show as a delete plus an add.
  • The SVG estimates text width from a monospace font. If a viewer substitutes a proportional font, long labels may sit closer to their neighbours.
  • Relative ages (last 4d) use the local clock; the header dates use the local time zone and JSON uses UTC.

License

MIT

Documentation

Overview

Command commitmap turns git history into a map of where work actually happened.

Directories

Path Synopsis
internal
cli
Package cli wires flags, git and the renderers together.
Package cli wires flags, git and the renderers together.
conv
Package conv parses Conventional Commits subjects: type(scope)!: subject.
Package conv parses Conventional Commits subjects: type(scope)!: subject.
gitlog
Package gitlog runs `git log` and parses its NUL-separated output as a stream.
Package gitlog runs `git log` and parses its NUL-separated output as a stream.
group
Package group turns parsed commits into a tree for each grouping mode.
Package group turns parsed commits into a tree for each grouping mode.
mapper
Package mapper builds a commit map report from a repository.
Package mapper builds a commit map report from a repository.
mcp
Package mcp is a small Model Context Protocol server over stdio, written against the standard library only.
Package mcp is a small Model Context Protocol server over stdio, written against the standard library only.
mcptools
Package mcptools exposes commitmap as MCP tools.
Package mcptools exposes commitmap as MCP tools.
mergemsg
Package mergemsg extracts branch names from merge commit subjects.
Package mergemsg extracts branch names from merge commit subjects.
render
Package render writes a commit map as text, JSON, SVG or HTML.
Package render writes a commit map as text, JSON, SVG or HTML.
tree
Package tree holds the commit-count tree that every grouping mode produces.
Package tree holds the commit-count tree that every grouping mode produces.

Jump to

Keyboard shortcuts

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