Documentation/technical: add paint-down-to-common doc
Add a technical document describing the paint_down_to_common() algorithm used for merge-base computation, covering the paint walk, generation number regions, and termination conditions. Signed-off-by: Kristofer Karlsson <krka@spotify.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Kristofer Karlsson committed
Jul 11, 2026 at 13:27 UTC
6001fcbed30f3b0ae2f9ab6df4e391fdb7b90959
4 files changed
+182
-1
Documentation/Makefile
+1
@@ -129,6 +129,7 @@ TECH_DOCS += technical/long-running-process-protocol
129
TECH_DOCS += technical/multi-pack-index
130
TECH_DOCS += technical/packfile-uri
131
TECH_DOCS += technical/pack-heuristics
132
+TECH_DOCS += technical/paint-down-to-common
133
TECH_DOCS += technical/parallel-checkout
134
TECH_DOCS += technical/partial-clone
135
TECH_DOCS += technical/platform-support
Documentation/technical/meson.build
+1
@@ -18,6 +18,7 @@ articles = [
18
'multi-pack-index.adoc',
19
'packfile-uri.adoc',
20
'pack-heuristics.adoc',
21
+ 'paint-down-to-common.adoc',
22
'parallel-checkout.adoc',
23
'partial-clone.adoc',
24
'platform-support.adoc',
Documentation/technical/paint-down-to-common.adoc
new
+175
@@ -0,0 +1,175 @@
1
+Merge-Base Computation and paint_down_to_common()
2
+==================================================
3
+
4
+The function `paint_down_to_common()` in `commit-reach.c` computes merge
5
+bases by walking the commit graph backwards from two sets of tips and
6
+finding where their ancestry meets.
7
+
8
+Use cases
9
+---------
10
+
11
+Computing merge bases is used in two different ways:
12
+
13
+ 1. *Finding all merge bases* (`merge-base --all`, `merge-tree`,
14
+ `merge`, `rebase`). A merge base is a common ancestor that is
15
+ not itself an ancestor of another common ancestor.
16
+
17
+ 2. *Ancestry checks* (`in_merge_bases`, used by `merge-base
18
+ --is-ancestor`, `branch -d`, `fetch`). These ask: "is commit A
19
+ an ancestor of commit B?" If a common ancestor equals one of the
20
+ inputs, that input is necessarily the only merge base -- no other
21
+ common ancestor can be both as recent and not an ancestor of it.
22
+
23
+Both use cases share the same algorithm and implementation.
24
+
25
+Algorithm
26
+---------
27
+
28
+Given a commit `one` and a set of commits `twos[]`, the walk paints
29
+commits with two colors:
30
+
31
+ - PARENT1: reachable from `one`
32
+ - PARENT2: reachable from any commit in `twos[]`
33
+
34
+The walk uses a priority queue ordered by generation number
35
+(highest first), breaking ties by commit date. Each step dequeues
36
+the highest-priority commit (this is when we say a commit is
37
+"visited") and propagates its paint flags to its parents, enqueuing
38
+them if they gained new flags. When a commit receives both PARENT1
39
+and PARENT2, it is a merge-base candidate. A candidate gains the
40
+STALE flag so its ancestors propagate staleness -- any deeper common
41
+ancestor is necessarily redundant.
42
+
43
+[[generation-regions]]
44
+INFINITY and finite generation regions
45
+--------------------------------------
46
+
47
+The properties in this section assume generation-number ordering (the
48
+default comparator). They do NOT hold when the date-ordering fallback
49
+is active -- see <<date-ordering-fallback>>.
50
+
51
+The commit-graph stores a generation number for each commit.
52
+Commits not in the commit-graph have generation
53
+`GENERATION_NUMBER_INFINITY`. The graph is closed under
54
+reachability: if a commit is in the graph, all its ancestors are
55
+too. This partitions the commit graph into two regions:
56
+
57
+....
58
+ +---------------------------------------+
59
+ | INFINITY region |
60
+ | generation = INFINITY |
61
+ | queue order: heuristic (commit date) |
62
+ +---------------------------------------+
63
+ |
64
+ v
65
+ +---------------------------------------+
66
+ | Finite region |
67
+ | generation = finite |
68
+ | queue order: topological |
69
+ +---------------------------------------+
70
+....
71
+
72
+When the commit-graph is enabled, the INFINITY region is typically
73
+very small -- it only contains commits added since the last
74
+commit-graph refresh.
75
+
76
+All reachable INFINITY-generation commits are visited before any
77
+finite-generation commit, because INFINITY is larger than any finite
78
+value. Once the walk crosses into the finite region, it stays there.
79
+
80
+In the finite region, generation ordering guarantees topological
81
+traversal: children are always visited before their parents. This
82
+means that paint on already-visited commits is final -- no future
83
+traversal step can add paint to them.
84
+
85
+In the INFINITY region, commit-date ordering can violate this: a
86
+parent with a later date can be visited before a child with an earlier
87
+date. Paint flags are therefore NOT final at visit time, and a
88
+commit visited with only one side's paint may later gain the other.
89
+
90
+Paint flags are only added, never removed. Since each flag can be set
91
+at most once per commit, the number of times a commit can be
92
+re-enqueued is bounded by the number of flag transitions.
93
+
94
+Termination
95
+-----------
96
+
97
+The walk uses a `nonstale_queue` wrapper around `prio_queue` that
98
+tracks `max_nonstale`: the lowest-priority non-stale commit enqueued
99
+so far. Once that commit is dequeued, every remaining entry is known
100
+to be STALE and the loop terminates. Specifically, the main loop
101
+ends when one of the following conditions holds:
102
+
103
+ 1. The queue is empty.
104
+ 2. `max_nonstale` has been dequeued, meaning the queue only contains
105
+ STALE entries.
106
+ 3. Generation cutoff: the dequeued commit's generation is below
107
+ a caller-supplied `min_generation` threshold.
108
+ 4. Single result: the caller only needs one merge base, one has
109
+ been found, and the walk has entered the finite-generation
110
+ region.
111
+
112
+Stale entry condition
113
+~~~~~~~~~~~~~~~~~~~~~
114
+Once all queued entries are stale, no new merge-base candidates can
115
+be discovered -- that requires at least one non-stale commit from
116
+each side meeting. Continuing the walk could still invalidate
117
+existing candidates by proving one is an ancestor of another, but
118
+`remove_redundant()` handles that as a post-processing step, so it
119
+is safe to exit early.
120
+
121
+Generation cutoff
122
+~~~~~~~~~~~~~~~~~
123
+Some callers (notably `remove_redundant()`) supply a `min_generation`
124
+threshold -- the minimum generation of the input commits. No merge
125
+base can have a generation below this threshold, so the walk
126
+terminates as soon as it dequeues such a commit.
127
+
128
+Single result
129
+~~~~~~~~~~~~~
130
+When only one merge base is needed, the walk is in the
131
+finite-generation region, and the queue uses generation ordering,
132
+the first candidate found is necessarily the highest-generation
133
+common ancestor. No remaining commit in the queue can be a
134
+descendant of this candidate (generation ordering guarantees
135
+children are visited first), so it cannot be redundant and the walk
136
+can stop immediately.
137
+
138
+This optimization is NOT safe when the date-ordering fallback is
139
+active, because commit-date order can visit a deeper ancestor
140
+before a shallower one -- see <<date-ordering-fallback>>.
141
+
142
+[[date-ordering-fallback]]
143
+Date-ordering fallback
144
+----------------------
145
+
146
+When the commit-graph has generation numbers v1 and no
147
+generation floor is specified, topological ordering
148
+(via generation numbers) is disabled. Topological levels are
149
+correct but unbalanced -- ordering by such generation numbers
150
+can sometimes cause the walk to detour too far before finding
151
+merge bases. Commit-date ordering typically reaches them in
152
+fewer steps -- see this change for more details:
153
+
154
+ 091f4cf3 (commit: don't use generation numbers if not needed,
155
+ 2018-08-30)
156
+
157
+With generation number v2 (corrected commit dates) we have the best
158
+of both worlds and do not need this fallback.
159
+
160
+For v1, `paint_down_to_common()` falls back to pure commit-date
161
+ordering via `compare_commits_by_commit_date`. Because commit
162
+dates are not monotonic (clock skew, rebases, etc.), the queue
163
+may visit commits out of topological order.
164
+
165
+This disables the optimization that depends on generation ordering:
166
+
167
+ - *Single result*: the first merge-base candidate found may not
168
+ be the shallowest, because a deeper ancestor with a higher
169
+ commit date can be dequeued first.
170
+
171
+Related documentation
172
+---------------------
173
+
174
+ - `Documentation/technical/commit-graph.adoc` -- generation numbers
175
+ and the reachability closure property.
commit-reach.c
+5
-1
@@ -96,7 +96,11 @@ static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue)
96
return commit;
97
}
98
99
-/* all input commits in one and twos[] must have been parsed! */
99
+/*
100
+ * See Documentation/technical/paint-down-to-common.adoc
101
+ *
102
+ * All input commits in one and twos[] must have been parsed!
103
+ */
104
static int paint_down_to_common(struct repository *r,
105
struct commit *one, int n,
106
struct commit **twos,