Git Bench

five git implementations, one repository, the same six operations.

Every runner is held to the same work contract on a full clone of vercel/next.js, so the numbers compare implementations rather than workloads. bun:ffi + libgit2 finishes 4.2× faster than the git CLI, and 356× separates the fastest runner from the slowest.

5 runners · 6 operations · 5 timed samples each · last run

A git history drawn in four stages: a single commit, a linear history, a branch off it, and the merge that closes the branch.
  1. 01Commit
  2. 02History
  3. 03Branch
  4. 04Merge

Overall ranking

Geometric mean of the six operations against the git CLI, on a logarithmic axis: lower is faster, a bar left of parity beats git, and the whisker spans the runner’s best and worst operation.

Which library should you use?

Recommendations are derived from the published results and update when the benchmark changes.

  • Best overall

    bun:ffi + libgit2

    Lowest geometric mean across all six operations, normalized against the git CLI.

  • Best in-process option

    bun:ffi + libgit2

    Avoids subprocess startup when the benchmark work is embedded in your application.

  • Most reliable

    gitoxide

    Fewest parity mismatches and failed operations, with the smallest average sample spread.

Operations

How far apart the implementations land on each operation: the bar is the slowest median divided by the fastest, and the whisker is that ratio at its most and least favourable across the samples.

OperationSpread
  • Log (100)Walk the 100 most recent commits from HEAD.12,785× ±24%0.26ms
  • Read 25 blobsRead 25 file blobs at HEAD by path.7,802× ±9%10ms
  • Changed filesName-only diff between HEAD~1 and HEAD.4,226× ±5%2.33ms
  • Tracked filesEnumerate every path tracked in the index.210× ±49%1.30ms
  • Current branchResolve HEAD to a branch name.149× ±62%0.04ms
  • StatusWorktree + index status (porcelain).21× ±5%364ms
See every runner and number

Speed against consistency

Horizontal axis
Score, lower is faster, against how far the same runner drifts between its best and worst operation. Both axes logarithmic; the brighter lines mark parity with git.
Score:
the geometric mean of the six operations, each as a multiple of the git CLI.
Spread:
the worst operation divided by the best. A point far to the right hides at least one operation you would not want in a request path.

Full results

Median time per operation; sort by any runner to find what it handles worst.

Unit
Operation

Current branch

1.47ms1.0×1.37ms–1.63ms0.04ms0.03×0.03ms–0.06ms5.90ms4.0×5.73ms–7.11ms3.64ms2.5×3.20ms–6.02ms1.49ms1.0×1.31ms–2.58ms

Status

407ms1.0×405ms–421ms687ms1.7×685ms–688ms364ms0.89×350ms–377ms7.59s19×7.44s–7.67s1.85s4.5×1.83s–1.85s

Log (100)

5.07ms1.0×4.10ms–5.94ms0.26ms0.05×0.22ms–0.35ms12ms2.3×12ms–12ms3.32s655×3.28s–3.36s11ms2.1×10ms–11ms

Tracked files

17ms1.0×17ms–19ms1.30ms0.08×0.90ms–2.66ms273ms16×269ms–274ms36ms2.1×34ms–50ms16ms0.92×15ms–16ms

Changed files

2.33ms1.0×2.24ms–2.41ms14ms5.8×13ms–14ms30ms13×29ms–37ms9.83s4,226×9.69s–9.88s3.11s1,338×3.11s–3.12s

Read 25 blobs

59ms1.0×57ms–64ms10ms0.18×9.56ms–11ms155ms2.6×151ms–165ms81.45s1,377×79.53s–82.43s64ms1.1×64ms–65ms

Methodology

  • Every runner executes in the same Docker image against one clone of vercel/next.js: one warmup iteration is discarded, 5 timed samples follow, and the median is published.
  • Runners are held to the same work contract — status collects untracked but not ignored files, blob reads materialize full contents — and any disagreement with the git CLI baseline is logged as a parity mismatch rather than counted as a win.
  • The score is the geometric mean of each operation's ratio to the git CLI, so no single slow operation decides the aggregate.
  • Numbers come from a GitHub Actions runner, so the comparison between runners is the signal and the milliseconds are context.
CPU
AMD EPYC 7763 64-Core Processor
Runtime
Bun 1.4.0
Source
github-actions

Run against 8057603. Raw results, config schema and the source.