1. The queue is empty.
2. The queue contains only stale entries.
+ 3. Side exhaustion: no pure PARENT1 or pure PARENT2 commits
+ remain in the queue, no pending merge-base candidates exist,
+ and the walk has entered the finite-generation region.
Stale entry condition
~~~~~~~~~~~~~~~~~~~~~
`remove_redundant()` handles that as a post-processing step, so it
is safe to exit early.
+Side-exhaustion condition
+~~~~~~~~~~~~~~~~~~~~~~~~~
+A new merge-base requires commits from both sides to meet. When one
+side's exclusive counter reaches zero and there are no pending
+merge-base candidates, no future traversal step can produce a new
+candidate.
+
+This optimization only activates in the finite-generation region
+where topological ordering holds. In that region, children are
+always visited before parents, so paint flags are final at visit
+time and an exhausted side cannot reappear. In the INFINITY region,
+commit-date ordering can violate this guarantee, so the check is
+skipped.
+
Related documentation
---------------------
static struct commit *paint_queue_get(struct paint_state *state)
{
- struct commit *commit;
+ struct commit *commit = prio_queue_get(&state->queue);
- if (!state->p1_count && !state->p2_count &&
- !state->pending_merge_bases)
+ if (!commit)
return NULL;
- commit = prio_queue_get(&state->queue);
- if (commit) {
- commit->object.flags &= ~ENQUEUED;
- paint_count_update(state, commit->object.flags, -1);
+ commit->object.flags &= ~ENQUEUED;
+
+ if (!state->pending_merge_bases) {
+ if (!state->p1_count && !state->p2_count)
+ return NULL;
+ /*
+ * Side exhaustion: a new merge-base can only form
+ * when both PARENT1-only and PARENT2-only commits
+ * remain in the queue. In the finite-generation
+ * region the queue is ordered topologically, so
+ * no future step can add paint to visited commits
+ * and an exhausted side cannot reappear.
+ */
+ if ((!state->p1_count || !state->p2_count) &&
+ commit_graph_generation(commit) < GENERATION_NUMBER_INFINITY)
+ return NULL;
}
+
+ paint_count_update(state, commit->object.flags, -1);
return commit;
}
cp commit-graph-full .git/objects/info/commit-graph &&
GIT_TRACE2_EVENT="$(pwd)/trace-full.txt" \
git merge-base --all commit-9-9 commit-9-1 >actual &&
- test_trace2_data paint_down_to_common steps 80 <trace-full.txt &&
+ test_trace2_data paint_down_to_common steps 9 <trace-full.txt &&
cp commit-graph-half .git/objects/info/commit-graph &&
GIT_TRACE2_EVENT="$(pwd)/trace-half.txt" \
git merge-base --all commit-9-9 commit-9-1 >actual &&
- test_trace2_data paint_down_to_common steps 81 <trace-half.txt
+ test_trace2_data paint_down_to_common steps 57 <trace-half.txt
'
test_expect_success 'reduce_heads' '