Enabling the commit-graph and multi-pack-index Jump to heading
Two of Gitβs slowest operations in a large repository are walking history and finding objects. Walking history β for git log --graph, git merge-base, ahead/behind counts, git branch --contains β normally means parsing commit objects one at a time from compressed packs. Finding an object means checking the index of every pack file in turn, and a busy repository can accumulate dozens of packs between repacks. The commit-graph file stores commit parents, root trees and generation numbers in a compact, directly indexable form, so history walks can skip most parsing and stop early. The multi-pack-index (MIDX) is a single index across all pack files, so lookups check one index instead of many. Both are safe, additive files Git can always rebuild. This page enables them, verifies they are used, and keeps them current, within large repository performance.
When to use this approach Jump to heading
git log --graph,git statusahead/behind counts orgit merge-baseare slow.- The repository has hundreds of thousands of commits or many pack files.
- Server-side operations such as reachability checks during push take noticeably long.
- You already schedule maintenance, as in scheduling git maintenance for background repacking.
Step 1 β Measure the operations you want to speed up Jump to heading
Time representative commands before changing anything, so you can tell whether the files help and by how much.
git count-objects -v | grep -E 'count|packs|size-pack'
ls .git/objects/pack/*.pack | wc -l
time git log --oneline --graph -n 2000 >/dev/null
time git merge-base origin/main HEAD
time git rev-list --count origin/main
time git branch -r --contains HEAD~500 >/dev/null Step 2 β Write the commit-graph Jump to heading
Write a commit-graph covering all reachable commits, and enable reading it. Recent Git versions read it by default; the config makes the intent explicit and enables writing it during fetch.
git config core.commitGraph true
git config fetch.writeCommitGraph true # update incrementally after each fetch
git commit-graph write --reachable --changed-paths
git commit-graph verify
ls -la .git/objects/info/commit-graph* .git/objects/info/commit-graphs 2>/dev/null --changed-paths adds Bloom filters recording which paths each commit changed, which speeds up git log -- <path> and git blame considerably in large trees.
Step 3 β Write the multi-pack-index Jump to heading
Enable the multi-pack-index and write it over the current packs.
git config core.multiPackIndex true
git multi-pack-index write --bitmap # bitmap speeds up reachability for clones and fetches served from here
git multi-pack-index verify
ls -la .git/objects/pack/multi-pack-index* The bitmap option matters most on servers and mirrors that serve clones; on a developer machine the index alone is the main win.
Step 4 β Re-measure Jump to heading
Repeat the timings from Step 1. The largest improvements usually appear in history walks over long ranges and path-limited log.
time git log --oneline --graph -n 2000 >/dev/null
time git log --oneline -- services/billing/ >/dev/null
time git branch -r --contains HEAD~500 >/dev/null Step 5 β Keep the files current Jump to heading
Both files go stale as new commits and packs arrive; Git still works, but new data is not covered. git maintenance updates them incrementally on a schedule.
git maintenance start # registers the repo, enables scheduled tasks
git config maintenance.commit-graph.enabled true
git config maintenance.incremental-repack.enabled true # also rewrites the multi-pack-index
git maintenance run --task=commit-graph --task=incremental-repack On servers, run the same tasks from the hosting platformβs maintenance jobs or a cron entry, and make sure the serverβs Git version supports the bitmap format you write.
Step 6 β Roll it out to every clone Jump to heading
These files are local to each clone; they are not pushed or fetched. Make them part of developer setup and CI runner images.
# In the team bootstrap script
git config --global fetch.writeCommitGraph true
git config --global core.commitGraph true
git config --global core.multiPackIndex true
for r in "$HOME"/src/*/; do git -C "$r" maintenance register 2>/dev/null; done Machine setup is covered in bootstrapping a developer machine for Git. CI runners that reuse a mirror benefit too, as in reusing a Git mirror on self-hosted runners.
Step 7 β Recover from a corrupt or mismatched file Jump to heading
Because both files are derived, the fix for any problem is to delete and rewrite them. Symptoms are errors mentioning commit-graph or multi-pack-index, often after copying a repository between machines with different Git versions.
git commit-graph verify || { rm -rf .git/objects/info/commit-graph .git/objects/info/commit-graphs; git commit-graph write --reachable --changed-paths; }
git multi-pack-index verify || { rm -f .git/objects/pack/multi-pack-index*; git multi-pack-index write; } Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does this change the repository content or history? Jump to heading
No. Both files are indexes derived from the objects. Deleting them returns the repository to exactly its previous state, only slower.
Is the commit-graph used by default now? Jump to heading
Reading it is on by default in current Git versions, and git gc writes it. Enabling fetch.writeCommitGraph and maintenance keeps it current between garbage collections, which is where most clones fall behind.
Do hosted forges do this for us? Jump to heading
Forges maintain their own server-side copies. Your local clones still need their own files, because none of this is transferred by fetch or clone.
Related Jump to heading
- Large Repository Performance β the parent topic.
- Diagnosing a Slow git status β the working-tree side of performance.
- Using Scalar for Very Large Repositories β configures all of this in one command.
- Tracing a Function History with git log -L β history queries that benefit from changed-path filters.