Skip to content

Repository files navigation

git-own

Git contributor statistics CLI — scan a repository and see who wrote what, how much they own, and when they were active.

npm version license node


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

Table of contents

Features

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

Install

Via npm

npx git-own [path] [options]          # run without installing
npm install -g git-own                # install globally
git-own [path] [options]

From source

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]

Quick start

# 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-name

Usage

Run 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.0

Output formats

git-own -f json       # machine-readable (totals + authors array)
git-own -f csv        # spreadsheet-friendly, one row per author
git-own -f markdown   # GitHub-rendered table
JSON 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_

Sort, limit, and filter

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 duplicates

Sort keys: pct (default) · commits · additions · deletions · files · name

Time and revision filtering

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 date

How Code% works with filters:

When --since, --until, --from, or --to is used, Code% reflects only code written during the filtered period. Blame lines whose originating commit falls outside the range are not counted. So git-own --since 2025-01-01 shows each author's share of the code written in 2025, not their share of the entire codebase.

When --to or --until is 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-inactive to show them.

Piping

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.md

File exclusions

Generated 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-own
Full 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.

Config file

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.

Merging duplicate authors

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.

How it works

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.

Options reference

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

Limitations

  • Submodules are not descended; only the top-level repository is scanned.
  • Binary files are skipped in the blame pass by default. Use --include-binary to attempt them anyway.
  • Shallow clones have incomplete history; a warning is printed on stderr.
  • Merge commits are excluded by default. Use --merges to include them.
  • --merge-by-name merges by exact (case-insensitive) name match only. Different-name variants of the same person require a .mailmap file.

License

MIT © Ahmed Semih Erkan

About

Git Contributor Statistics CLI — scan a repository and see who wrote what, how much they own, and when they were active. Per-author commits, lines, files, active days, and current code ownership.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages