Introduction
"Reachability" is the most important property of a Git object. An object is reachable if some ref leads to it via parent or tree links. Unreachable objects are eligible for garbage collection.
Reachability rules
Starting from any ref:
- A commit reaches its root tree, all parents, and (transitively) those parents' trees and parents.
- A tree reaches all entries (blobs and subtrees).
- An annotated tag reaches the tagged object.
Refs include local branches, remote-tracking branches, tags, stash, notes, and the reflog (until expiration).
Listing reachable objects
git rev-list --objects --all | head
git rev-list --objects HEAD
git rev-list --count HEAD
Ancestry tests
git merge-base --is-ancestor X Y && echo "X is ancestor of Y"
git rev-list X..Y --count # how many commits lie between
git log --ancestry-path A..B --oneline # only commits on path A to B
--ancestry-path is invaluable for finding the chain of commits that brought a fix from one tag to another.
Range operators
A..B: reachable from B, not from A.A...B: symmetric difference (either side, not both).^A B C: reachable from B or C but not A.
git log main..feature # commits added by feature
git log main...feature --left-right # both sides labelled
Unreachable objects
git fsck --unreachable
git fsck --dangling
git fsck --lost-found # writes them to .git/lost-found
Unreachable objects survive until git gc --prune removes them; the default grace period is two weeks (gc.pruneExpire).
The role of the commit-graph file
Reachability and ancestry queries are the bottleneck of many Git operations. The commit-graph file precomputes generation numbers and parent lists, making these queries O(log n) instead of O(n):
git commit-graph write --reachable
git config --global core.commitGraph true
Bitmap reachability
Server-side, reachability bitmaps let Git answer "what objects are reachable from this commit?" in microseconds, by storing a precomputed bit per object. They are written by:
git repack -adb
Walks limited by date or path
The reachability machinery accepts many limiters that combine with range syntax:
git rev-list --since=2024-01-01 main
git rev-list main -- path/to/file
git rev-list --no-merges main..feature
git rev-list --first-parent main
git rev-list HEAD --count
Combine these to answer specific questions like "how many non-merge commits did Ada land on main last quarter?" without resorting to shell pipelines. Plumbing's stable output makes the result trivial to feed into reporting tools.
Common mistakes
Believing a deleted branch erases its commits; they remain reachable through the reflog for 90 days, and unreachable but unpruned for two more weeks. Confusing A..B with A...B; one is asymmetric, the other symmetric. Using HEAD~3 to navigate across merges and being confused by first-parent traversal; use HEAD^2 to switch to the second parent. Finally, expecting git fsck to find lost commits after a fresh clone; the reflog and dangling objects only exist locally where the work was done.