By admin , 28 April 2026

Introduction

A Git commit references zero, one, or several parent commits. The whole history is a directed acyclic graph (DAG) of these parent links. Branches and tags are simply labels on nodes of this graph.

Anatomy of a parent link

git cat-file -p HEAD
# tree 9f1a...
# parent b2c3...
# parent d4e5...        (only on merge commits)
# author Ada ...
# committer Ada ...

One parent: linear history. Two parents: a merge. Zero parents: a root commit. Three or more: an octopus merge.

Walking the graph

git log --oneline --graph --all
git log --first-parent           # follow only first parents
git log --topo-order
git log --date-order

--first-parent on main is great for seeing the "trunk" of history without merge-side noise.

Ancestry queries

git merge-base A B               # most recent common ancestor
git merge-base --is-ancestor X Y   # exit 0 if X is ancestor of Y
git rev-list A..B --count          # commits in B not in A

Range syntax

  • A..B: commits reachable from B but not A.
  • A...B: symmetric difference.
  • ^A B: same as A..B.
  • B --not A: same as A..B.
git log main..feature
git log main...feature --left-right --oneline

The commit-graph file

Since Git 2.18, Git can write a commit-graph file that pre-computes ancestry data for huge repos:

git commit-graph write --reachable
git config --global core.commitGraph true
git config --global gc.writeCommitGraph true

This dramatically speeds up git log --graph, git merge-base, and reachability checks.

Octopus merges

A single merge commit can have many parents:

git merge feature1 feature2 feature3

Octopus merges only succeed without conflicts. They are useful for integration branches in some workflows but rarely needed.

Visualizing the graph

git log --oneline --graph --decorate --all
gitk --all
git log --pretty=format:'%h %p %s'

%p prints parent SHAs.

Generation numbers

The commit-graph file stores a generation number per commit: 1 for root commits, max(parent generations) + 1 otherwise. Generation numbers turn many ancestry queries into O(log n) operations. Modern Git (2.27+) writes corrected commit dates and generation v2 numbers when the commit-graph is enabled:

git commit-graph write --reachable --changed-paths
git config gc.writeCommitGraph true
git config core.commitGraph true
git log --oneline --graph -n 5

--changed-paths additionally records a Bloom filter per commit listing changed paths, dramatically speeding up path-limited git log queries.

Common mistakes

Picturing branches as separate stacks of commits; they are labels on nodes that may share ancestors. Using HEAD~3 on a merge commit and being surprised: ~ always follows the first parent. Use ^2, ^3 to jump to other parents. Forgetting that rebase rewrites graph topology by creating new commits with new parent pointers; old commits live on in the reflog. Finally, thinking the commit graph stores diffs; it stores trees and parents only. Diffs are computed on demand by comparing trees.