Skip to content

Commit d84b932

Browse files
committed
commit-reach: terminate merge-base walk when one paint side is exhausted
Add an early termination check to paint_down_to_common() using the per-side counters introduced earlier. Once the walk enters the finite-generation region, terminate early when one side's exclusive count drops to zero -- no new merge-base can form without both paint sides meeting. The check also waits for pending_merge_bases to reach zero, ensuring all merge-base candidates have been dequeued and recorded before exiting. The INFINITY gate ensures correctness: commits without a commit-graph entry have GENERATION_NUMBER_INFINITY and are ordered by commit date, which is not topologically reliable. The optimization only fires once the walk enters the finite-generation region where ordering guarantees hold. Step counts measured with trace2 on git.git with commit-graph: merge-base --all v2.0.0 v2.55.0-rc1: before: 72264 steps after: 44589 steps merge-base --all v2.55.0-rc1 v2.55.0-rc1~5: before: 110 steps after: 7 steps Helped-by: Derrick Stolee <stolee@gmail.com> Helped-by: Elijah Newren <newren@gmail.com> Signed-off-by: Kristofer Karlsson <krka@spotify.com>
1 parent 8c72f01 commit d84b932

3 files changed

Lines changed: 39 additions & 9 deletions

File tree

Documentation/technical/paint-down-to-common.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ ends when one of the following conditions holds:
9494
9595
1. The queue is empty.
9696
2. The queue contains only stale entries.
97+
3. Side exhaustion: no pure PARENT1 or pure PARENT2 commits
98+
remain in the queue, no pending merge-base candidates exist,
99+
and the walk has entered the finite-generation region.
97100
98101
Stale entry condition
99102
~~~~~~~~~~~~~~~~~~~~~
@@ -104,6 +107,20 @@ existing candidates by proving one is an ancestor of another, but
104107
`remove_redundant()` handles that as a post-processing step, so it
105108
is safe to exit early.
106109
110+
Side-exhaustion condition
111+
~~~~~~~~~~~~~~~~~~~~~~~~~
112+
A new merge-base requires commits from both sides to meet. When one
113+
side's exclusive counter reaches zero and there are no pending
114+
merge-base candidates, no future traversal step can produce a new
115+
candidate.
116+
117+
This optimization only activates in the finite-generation region
118+
where topological ordering holds. In that region, children are
119+
always visited before parents, so paint flags are final at visit
120+
time and an exhausted side cannot reappear. In the INFINITY region,
121+
commit-date ordering can violate this guarantee, so the check is
122+
skipped.
123+
107124
Related documentation
108125
---------------------
109126

commit-reach.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,30 @@ static void paint_queue_put(struct paint_state *state,
133133

134134
static struct commit *paint_queue_get(struct paint_state *state)
135135
{
136-
struct commit *commit;
136+
struct commit *commit = prio_queue_get(&state->queue);
137137

138-
if (!state->p1_count && !state->p2_count &&
139-
!state->pending_merge_bases)
138+
if (!commit)
140139
return NULL;
141140

142-
commit = prio_queue_get(&state->queue);
143-
if (commit) {
144-
commit->object.flags &= ~ENQUEUED;
145-
paint_count_update(state, commit->object.flags, -1);
141+
commit->object.flags &= ~ENQUEUED;
142+
143+
if (!state->pending_merge_bases) {
144+
if (!state->p1_count && !state->p2_count)
145+
return NULL;
146+
/*
147+
* Side exhaustion: a new merge-base can only form
148+
* when both PARENT1-only and PARENT2-only commits
149+
* remain in the queue. In the finite-generation
150+
* region the queue is ordered topologically, so
151+
* no future step can add paint to visited commits
152+
* and an exhausted side cannot reappear.
153+
*/
154+
if ((!state->p1_count || !state->p2_count) &&
155+
commit_graph_generation(commit) < GENERATION_NUMBER_INFINITY)
156+
return NULL;
146157
}
158+
159+
paint_count_update(state, commit->object.flags, -1);
147160
return commit;
148161
}
149162

t/t6600-test-reach.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,12 @@ test_expect_success 'merge-base --all commit-walk steps' '
332332
cp commit-graph-full .git/objects/info/commit-graph &&
333333
GIT_TRACE2_EVENT="$(pwd)/trace-full.txt" \
334334
git merge-base --all commit-9-9 commit-9-1 >actual &&
335-
test_trace2_data paint_down_to_common steps 80 <trace-full.txt &&
335+
test_trace2_data paint_down_to_common steps 9 <trace-full.txt &&
336336
337337
cp commit-graph-half .git/objects/info/commit-graph &&
338338
GIT_TRACE2_EVENT="$(pwd)/trace-half.txt" \
339339
git merge-base --all commit-9-9 commit-9-1 >actual &&
340-
test_trace2_data paint_down_to_common steps 81 <trace-half.txt
340+
test_trace2_data paint_down_to_common steps 57 <trace-half.txt
341341
'
342342

343343
test_expect_success 'reduce_heads' '

0 commit comments

Comments
 (0)