Build and Verify Git Commit-Graphs Safely
You will create a commit-graph in a Git repository, add changed-path Bloom filters when they are useful, verify the binary data against the object database, and know where Git stores the result. The examples use Git 2.43.0, the version installed with the local manpages.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow roughly five minutes for a small repository. A large repository may take much longer when Git computes changed paths, so schedule that step away from busy working hours if it is competing for disk or CPU.
Before you start
- Choose a repository where you can run Git commands as the repository owner.
- Check the Git version and repository state.
cd /path/to/repository
git --version
git rev-parse --show-toplevel
git status --short
Expected version output for the machine described by this guide is:
git version 2.43.0
A commit-graph is derived data under the repository's object directory. It does not change commits, branches, the index, or working-tree files. The write command can still consume substantial resources, and the files it creates should be included in any repository backup plan.
Checkpoint: write the graph
Start with all commits reachable from the repository's refs. The --changed-paths option also writes Bloom-filter data for paths changed between each commit and its first parent. This can make commands such as git log -- path/to/file faster, at the cost of more work while writing.
git commit-graph write --reachable --changed-paths --progress
On a terminal, progress is printed to standard error. A small repository normally ends with a message resembling:
Writing out commit graph: 100% (1/1), done.
The exact percentage and wording depend on the repository. If the command says that core.commitGraph is disabled, it may return success without writing anything. Check that setting before treating a no-op as a successful build:
git config --show-origin --get core.commitGraph || echo 'core.commitGraph is unset'
When it is explicitly false, enable it only for this repository with:
git config --local core.commitGraph true
That writes .git/config, not a system-wide file. To undo this explicit setting later, use:
git config --local --unset core.commitGraph
After unsetting it, Git's built-in default applies again. Do not use --global unless you deliberately want to affect other repositories.
Checkpoint: find the generated file
For a normal repository, the single graph is stored at .git/objects/info/commit-graph. A split graph uses a chain beneath .git/objects/info/commit-graphs/. Ask Git for the object directory rather than guessing where a linked worktree or alternate object store points:
object_dir=$(git rev-parse --git-path objects)
printf '%s\n' "$object_dir"
find "$object_dir/info" -maxdepth 2 -type f -name 'commit-graph*' -printf '%P %s bytes\n' 2>/dev/null
Typical output for the first command is an absolute path ending in /.git/objects. The graph is binary: do not edit it in a text editor, and do not infer validity from the output of file alone.
Checkpoint: verify against the object database
Run Git's verifier after writing the graph, and after restoring a repository from backup:
git commit-graph verify --progress
A successful run looks like this:
Verifying commits in commit graph: 100% (1/1), done.
The count reflects the repository, so yours will differ. Verification reads the graph and checks its contents against the object database. It is the useful integrity check; a file merely existing is not enough.
If the repository has a split chain and you need a quick tip-layer check, --shallow checks only the tip commit-graph. Use the full command above when you need the whole chain checked:
git commit-graph verify --shallow --progress
Use split graphs for incremental maintenance
A split graph stores layers below objects/info/commit-graphs. This is useful when you update a repository repeatedly and want new commits added as a tip layer. The following command never merges layers:
git commit-graph write --reachable --split=no-merge --changed-paths --progress
Git's default split strategy can merge layers according to its size rules. --split=replace deliberately replaces the existing chain with a new graph containing the selected commits. Treat that as a maintenance operation: it replaces derived files, but not repository history.
To rebuild all reachable commits and recompute older Bloom filters, use:
git commit-graph write --reachable --split=replace --changed-paths --progress
Do not run this repeatedly on a large repository without a reason. Changed-path computation can take a while. If you want to cap new filters, --max-new-filters=10000 limits filters generated for the new layer; the manpage documents -1 as unlimited. A cap trades some path-history acceleration for shorter maintenance work.
Adjust reading behaviour
Git 2.43.0 defaults commitGraph.readChangedPaths to true. Setting it false makes Git ignore changed-path Bloom filters even when they are present:
git config --local commitGraph.readChangedPaths false
git config --local --get commitGraph.readChangedPaths
Expected output is:
false
This changes reading behaviour, not the graph file. Restore the default by removing the local override:
git config --local --unset commitGraph.readChangedPaths
The other version-specific setting is commitGraph.generationVersion. The local Git documentation says the default is 2, which permits corrected commit dates in generation data. Version 1 omits that data when writing or reading. Leave the default unless you have a tested compatibility reason to change it:
git config --local commitGraph.generationVersion 2
git config --local --get commitGraph.generationVersion
Configuration changes are ordinary repository-local writes. They do not require root privileges. If another process owns or restricts the repository, resolve that filesystem permission issue rather than running the maintenance command as root, which can leave root-owned graph or configuration files behind.
Common failure traps
- Nothing appears after write: check
core.commitGraph; false disables writing while still allowing a successful return. - Bloom filters are missing: the graph may have been written without
--changed-paths, or an older layer may not contain them. Rebuild with--split=replace --changed-pathswhen the extra work is justified. - Verification fails: stop relying on the graph until the object database and graph are consistent. Preserve the diagnostic, check for incomplete restoration or damaged objects, and rebuild only after the objects themselves are available.
- A command appears slow: a full reachable walk and changed-path calculation are normal costs on a large history. Use a maintenance window and monitor available disk space.
- You delete the wrong thing: do not manually remove files while Git is writing. If you need to discard derived data, stop concurrent Git maintenance first, record the current file list, and prefer Git's split-writing options to manage the chain.
Done means
git commit-graph write --reachable --changed-paths --progresscompleted without an error or an unexplained no-op.- A graph exists under the object directory reported by
git rev-parse --git-path objects. git commit-graph verify --progresscompleted successfully.- Any
core.commitGraphorcommitGraph.*override is intentional and can be undone withgit config --local --unset.