Git contributor statistics CLI — scan a repository and see who wrote what, how much they own, and when they were active.
git-own analyzes any git repository and reports per-author statistics:
commits, lines added/removed, files touched, active days, and current code
ownership — the share of the codebase each author owns today, computed with
git blame.
$ git-own
┌────────────────────┬───────────────────────────────────┬─────────┬────────┬────────┬───────┬──────┬────────────┬────────────┬────────┐
│ Author │ Email │ Commits │ +Lines │ -Lines │ Files │ Days │ First │ Last │ Code% │
├────────────────────┼───────────────────────────────────┼─────────┼────────┼────────┼───────┼──────┼────────────┼────────────┼────────┤
│ Sindre Sorhus │ sindresorhus@gmail.com │ 187 │ 4,172 │ 3,984 │ 59 │ 115 │ 2013-08-03 │ 2025-09-08 │ 42.50% │
├────────────────────┼───────────────────────────────────┼─────────┼────────┼────────┼───────┼──────┼────────────┼────────────┼────────┤
│ Richie Bendall │ richiebendall@gmail.com │ 15 │ 376 │ 859 │ 18 │ 13 │ 2019-03-01 │ 2024-01-20 │ 15.05% │
├────────────────────┼───────────────────────────────────┼─────────┼────────┼────────┼───────┼──────┼────────────┼────────────┼────────┤
│ LitoMore │ litomore@users.noreply.github.com │ 5 │ 254 │ 123 │ 6 │ 5 │ 2022-06-26 │ 2022-10-31 │ 7.50% │
└────────────────────┴───────────────────────────────────┴─────────┴────────┴────────┴───────┴──────┴────────────┴────────────┴────────┘
Total: 71 authors · 348 commits · +9,127 / -6,761 lines · 1,661 blameable lines · 26 files · 6 excluded
- Features
- Install
- Quick start
- Usage
- File exclusions
- Config file
- Merging duplicate authors
- How it works
- Options reference
- Limitations
- License
| Category | What |
|---|---|
| Code ownership | Code% via git blame (whitespace-insensitive) — who owns the code now, not who wrote the most ever |
| History stats | Commits, +Lines, -Lines, files touched, active days, first/last commit — from a single streaming git log pass |
| Time filtering | --since/--until date filters and --from/--to revision ranges; Code% reflects only code written in the range |
| Author identity | Grouped by email; respects .mailmap; --merge-by-name for same-name duplicates |
| Author filter | --author to show only matching contributors (name or email substring) |
| File exclusions | Auto-excludes lock files, build output, minified code, vendored dirs; --exclude globs, .git-own-ignore, .gitattributes |
| Config file | .git-own.json for persistent defaults |
| Output formats | Pretty table (default), JSON, CSV, Markdown |
| Performance | Parallel blame (--concurrency, auto-on by default) |
| Sort & limit | By any metric; default sort is Code% descending |
npx git-own [path] [options] # run without installing
npm install -g git-own # install globally
git-own [path] [options]Requires Node.js 20+ and pnpm.
git clone https://github.com/ahmedsemih/git-own.git
cd git-own
pnpm install
pnpm build
node dist/index.js [path] [options]# scan the current directory
git-own
# scan a specific repo, show top 10 by commits
git-own /path/to/repo --top 10 -s commits
# who contributed in 2025?
git-own --since 2025-01-01 --until 2025-12-31
# export to JSON for scripting
git-own -f json | jq '.authors[] | {name, codePercent}'
# merge duplicate authors with same name
git-own --merge-by-nameRun from inside a repository (defaults to the current directory):
git-own
git-own /path/to/repo
git-own --branch main
git-own --branch v1.2.0git-own -f json # machine-readable (totals + authors array)
git-own -f csv # spreadsheet-friendly, one row per author
git-own -f markdown # GitHub-rendered tableJSON output example
{
"totals": {
"authors": 71,
"commits": 348,
"additions": 9127,
"deletions": 6761,
"blameableLines": 1661,
"files": 26,
"excludedFiles": 6
},
"authors": [
{
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"commits": 187,
"additions": 4172,
"deletions": 3984,
"filesTouched": 59,
"activeDays": 115,
"firstCommit": "2013-08-03T02:16:26+02:00",
"lastCommit": "2025-09-08T21:47:34+07:00",
"blameLines": 706,
"codePercent": 42.5045
}
]
}Markdown output example
| Author | Email | Commits | +Lines | -Lines | Files | Days | First | Last | Code% |
| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | --- | ---: |
| Sindre Sorhus | sindresorhus@gmail.com | 187 | 4172 | 3984 | 59 | 115 | 2013-08-03 | 2025-09-08 | 42.50% |
_Total: 71 authors · 348 commits · +9127 / -6761 lines · 1661 blameable lines · 26 files_git-own -s commits --top 10 # top 10 by commit count
git-own -s additions # most insertions across history
git-own -s name # alphabetical
git-own --author "sindre" # only matching authors (name or email)
git-own --author "gmail.com" # filter by email domain
git-own --merge-by-name # merge same-name/different-email duplicatesSort keys: pct (default) · commits · additions · deletions · files · name
git-own --since 2024-01-01 # commits from 2024 onwards
git-own --since 2024-01-01 --until 2024-12-31 # 2024 only
git-own --from v1.0 --to v2.0 # commits between two tags
git-own --until 2024-06-30 # everything up to a dateHow
Code%works with filters:When
--since,--until,--from, or--tois used,Code%reflects only code written during the filtered period. Blame lines whose originating commit falls outside the range are not counted. Sogit-own --since 2025-01-01shows each author's share of the code written in 2025, not their share of the entire codebase.When
--toor--untilis used, the blame pass also runs at that point in time (the range tip), not at HEAD.Authors with 0 commits in the filtered range are hidden by default. Use
--no-hide-inactiveto show them.
The spinner and diagnostics go to stderr; data goes to stdout — pipes just work:
git-own -f json | jq '.authors[] | {name, codePercent}'
git-own -f csv > contributors.csv
git-own -f markdown >> README.mdGenerated files like lock files and build output can dwarf real source code and skew stats. git-own excludes them from both the blame and history passes using four sources, combined:
| Source | What it covers | How to disable |
|---|---|---|
| Built-in defaults (on by default) | Lock files, build dirs (dist/, build/), minified code (*.min.js), source maps (*.map), vendored code (vendor/) |
--no-default-excludes |
.git-own-ignore |
A file at the repo root with .gitignore-style syntax (# comments, ! negation, trailing slash for dirs) |
Remove the file |
--exclude <glob> |
Repeatable CLI flag for ad-hoc exclusions (same gitignore semantics) | Don't pass it |
.gitattributes |
Files marked linguist-generated or -diff (GitHub's method for detecting generated files) |
--no-gitattributes |
# exclude everything under src/generated/
git-own --exclude 'src/generated/**'
# exclude all .sql files and a specific config
git-own -e '*.sql' -e 'config/secrets.json'
# turn off all automatic exclusions, analyze everything
git-own --no-default-excludes --no-gitattributes
# persist project-wide excludes
echo 'docs/' > .git-own-ignore
echo '*.gen.ts' >> .git-own-ignore
git-ownFull list of built-in default excludes
# Lock files
pnpm-lock.yaml package-lock.json npm-shrinkwrap.json yarn.lock
Cargo.lock Gemfile.lock composer.lock poetry.lock Pipfile.lock
go.sum flake.lock
# Build / output directories
dist/ build/ out/ coverage/ .next/ .nuxt/ .turbo/ .svelte-kit/
# Minified code
*.min.js *.min.css *.min.mjs *.min.cjs
# Source maps
*.map
# Vendored code
vendor/ third_party/ node_modules/
Patterns follow gitignore semantics: a pattern with no slash matches at any
depth, a trailing slash means the whole directory, and a leading slash anchors
to the repo root. Use -v to see how many files each source excluded.
A .git-own.json file at the repo root can set persistent defaults for any CLI
flag — no need to retype them every run.
{
"format": "table",
"sort": "commits",
"mergeByName": true,
"excludes": ["docs/**", "*.gen.ts"],
"concurrency": 4
}Precedence: explicit CLI flag > config file > built-in default
Use --no-config to skip reading the file. Verbose mode (-v) shows which
config file was loaded.
The same person may appear multiple times with different emails (e.g. a work email and a GitHub noreply email). Two methods to merge them:
| Method | Handles | How |
|---|---|---|
--merge-by-name |
Same name, different email | Auto-merges authors with the same case-insensitive name |
.mailmap |
All duplicate types (including different names) | Git's native author mapping file; respected automatically, no flag needed |
Both methods can be combined: --merge-by-name for the easy cases + .mailmap
for the tricky ones. See gitmailmap(5)
for .mailmap syntax.
git-own makes two passes over the repository:
┌──────────────────────────────────────────────────────────────────┐
│ History pass │
│ git log --no-merges -M -C --numstat --format=… │
│ → commits, +Lines, -Lines, files touched, active days, dates │
├──────────────────────────────────────────────────────────────────┤
│ Blame pass │
│ git ls-tree → filter → git blame --line-porcelain (parallel) │
│ → Code% (current ownership, filtered by in-range commits) │
└──────────────────────────────────────────────────────────────────┘
Code% is blameLines / totalBlameLines * 100 — the share of lines
attributed to each author. Without filtering, this is the share of the
current codebase; with filtering, it's the share of code written during
the filtered period.
+Lines / -Lines are historical totals (everything ever added/removed
in the range), so refactors that delete and rewrite code will inflate those
without changing Code%.
Author identity is grouped by lowercased email (name when email is empty).
.mailmap is always respected: git blame applies it automatically and the
log pass uses %aN/%aE, keeping the two passes consistent.
git-own [path] [options]
Arguments:
path path to the git repository to scan (default: cwd)
Options:
-b, --branch <rev> revision to scan (default: HEAD)
-f, --format <fmt> table | json | csv | markdown (default: table)
-s, --sort <key> pct | commits | additions | deletions | files | name
(default: pct)
--top <n> show only the top N authors
--author <names> show only matching authors (comma-separated)
--since <date> only count commits after this date
--until <date> only count commits before this date
--from <rev> start revision (exclusive) for rev-range analysis
--to <rev> end revision (inclusive) for rev-range analysis
--merges include merge commits (excluded by default)
--no-whitespace disable whitespace-insensitive blame
--include-binary also blame binary files (skipped by default)
-e, --exclude <glob> exclude files matching this glob (repeatable)
--no-default-excludes do not apply the built-in exclude patterns
--no-gitattributes do not respect .gitattributes generated-file markers
--merge-by-name merge authors with the same name (case-insensitive)
--concurrency <n> files to blame in parallel (default: min(8, CPUs))
--no-hide-inactive show authors with 0 commits in the filtered range
--no-config do not read .git-own.json config file
-v, --verbose show progress and diagnostics on stderr
-V, --version output the version number
-h, --help display help
- Submodules are not descended; only the top-level repository is scanned.
- Binary files are skipped in the blame pass by default. Use
--include-binaryto attempt them anyway. - Shallow clones have incomplete history; a warning is printed on stderr.
- Merge commits are excluded by default. Use
--mergesto include them. --merge-by-namemerges by exact (case-insensitive) name match only. Different-name variants of the same person require a.mailmapfile.
MIT © Ahmed Semih Erkan