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,