Merge branch 'kk/merge-base-exhaustion' into seen

The merge-base computation has been optimized by stopping the walk
early when one side's exclusive commits in the queue are exhausted,
yielding significant speedups for queries with one-sided histories.

* kk/merge-base-exhaustion:
  commit-reach: remove commit-date ordering fallback
  commit-reach: move min_generation check into paint_queue_get()
  commit-reach: terminate merge-base walk when one paint side is exhausted
  commit-reach: introduce struct paint_state with per-side counters
  t6600: add clock-skew topologies and step counts for edge cases
  commit-reach: add trace2 instrumentation to paint_down_to_common()
  t6099, t6600: add side-exhaustion regression tests
  t6600: add test cases for side-exhaustion edge cases
  test-lib-functions: improve diagnostic output for trace2 data assertions
  Documentation/technical: add paint-down-to-common doc
This commit is contained in:
Junio C Hamano
2026-08-07 14:48:25 -07:00
8 changed files with 632 additions and 54 deletions

View File

@@ -11,6 +11,7 @@
#include "tag.h"
#include "commit-reach.h"
#include "ewah/ewok.h"
#include "trace2.h"
/* Remember to update object flag allocation in object.h */
#define PARENT1 (1u<<16)
@@ -78,25 +79,106 @@ static void clear_nonstale_queue(struct nonstale_queue *queue)
queue->max_nonstale = NULL;
}
static void nonstale_queue_put_dedup(struct nonstale_queue *queue,
struct commit *c)
/*
* Priority queue with per-side commit counters for paint_down_to_common().
* Each non-stale queued commit occupies exactly one bucket: PARENT1-only,
* PARENT2-only, or both (a pending merge-base candidate).
*/
struct paint_state {
struct prio_queue queue;
size_t parent1_count;
size_t parent2_count;
size_t mb_candidate_count;
timestamp_t min_generation;
timestamp_t last_gen;
timestamp_t topo_ceiling;
};
static void paint_count_update(struct paint_state *state,
unsigned flags, int delta)
{
if (c->object.flags & ENQUEUED)
return;
c->object.flags |= ENQUEUED;
nonstale_queue_put(queue, c);
switch (flags & (PARENT1 | PARENT2 | STALE)) {
case PARENT1:
state->parent1_count += delta;
break;
case PARENT2:
state->parent2_count += delta;
break;
case PARENT1 | PARENT2:
state->mb_candidate_count += delta;
break;
case PARENT1 | PARENT2 | STALE:
break;
default:
BUG("unexpected paint state");
}
}
static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue)
static void paint_queue_put(struct paint_state *state,
struct commit *c, unsigned add_flags)
{
struct commit *commit = nonstale_queue_get(queue);
unsigned old_flags = c->object.flags;
c->object.flags |= add_flags;
if (commit)
commit->object.flags &= ~ENQUEUED;
if (old_flags & ENQUEUED) {
paint_count_update(state, old_flags, -1);
paint_count_update(state, c->object.flags, 1);
} else {
c->object.flags |= ENQUEUED;
prio_queue_put(&state->queue, c);
paint_count_update(state, c->object.flags, 1);
}
}
/*
* Dequeue the next commit for the paint walk, or return NULL when
* no more merge bases can be discovered.
*/
static struct commit *paint_queue_get(struct paint_state *state)
{
struct commit *commit = prio_queue_get(&state->queue);
timestamp_t generation;
if (!commit)
return NULL;
commit->object.flags &= ~ENQUEUED;
generation = commit_graph_generation(commit);
if (state->min_generation && generation > state->last_gen)
BUG("bad generation skip %"PRItime" > %"PRItime" at %s",
generation, state->last_gen,
oid_to_hex(&commit->object.oid));
state->last_gen = generation;
/* generation cutoff */
if (generation < state->min_generation)
return NULL;
if (!state->mb_candidate_count) {
/* only stale entries remain */
if (!state->parent1_count && !state->parent2_count)
return NULL;
/* one side is exhausted */
if ((!state->parent1_count || !state->parent2_count) &&
generation < state->topo_ceiling)
return NULL;
}
paint_count_update(state, commit->object.flags, -1);
return commit;
}
/* all input commits in one and twos[] must have been parsed! */
/*
* See Documentation/technical/paint-down-to-common.adoc
*
* All input commits in one and twos[] must have been parsed!
*/
static int paint_down_to_common(struct repository *r,
struct commit *one, int n,
struct commit **twos,
@@ -104,45 +186,40 @@ static int paint_down_to_common(struct repository *r,
enum merge_base_flags mb_flags,
struct commit_list **result)
{
struct nonstale_queue queue = {
{ compare_commits_by_gen_then_commit_date }
/*
* Generation ordering is required for the side-exhaustion and
* single-result early exits, which rely on topological traversal
* order (children visited before parents) in the finite region.
*/
struct paint_state state = {
.queue = { compare_commits_by_gen_then_commit_date }
};
struct commit *commit;
int i;
int gen_ordered = 1;
timestamp_t last_gen = GENERATION_NUMBER_INFINITY;
int steps = 0;
struct commit_list **tail = result;
if (!min_generation && !corrected_commit_dates_enabled(r)) {
queue.pq.compare = compare_commits_by_commit_date;
gen_ordered = 0;
}
state.min_generation = min_generation;
state.last_gen = GENERATION_NUMBER_INFINITY;
state.topo_ceiling = corrected_commit_dates_enabled(r)
? GENERATION_NUMBER_INFINITY
: GENERATION_NUMBER_V1_MAX;
one->object.flags |= PARENT1;
if (!n) {
commit_list_append(one, result);
return 0;
}
nonstale_queue_put_dedup(&queue, one);
paint_queue_put(&state, one, 0);
for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
nonstale_queue_put_dedup(&queue, twos[i]);
}
for (i = 0; i < n; i++)
paint_queue_put(&state, twos[i], PARENT2);
while (queue.max_nonstale) {
struct commit *commit = nonstale_queue_get_dedup(&queue);
while ((commit = paint_queue_get(&state))) {
struct commit_list *parents;
int flags;
timestamp_t generation = commit_graph_generation(commit);
if (min_generation && generation > last_gen)
BUG("bad generation skip %"PRItime" > %"PRItime" at %s",
generation, last_gen,
oid_to_hex(&commit->object.oid));
last_gen = generation;
if (generation < min_generation)
break;
steps++;
flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
if (flags == (PARENT1 | PARENT2)) {
@@ -155,8 +232,7 @@ static int paint_down_to_common(struct repository *r,
* descendant of this one.
*/
if (!(mb_flags & MERGE_BASE_FIND_ALL) &&
gen_ordered &&
generation < GENERATION_NUMBER_INFINITY)
state.last_gen < state.topo_ceiling)
break;
}
/* Mark parents of a found merge stale */
@@ -169,7 +245,7 @@ static int paint_down_to_common(struct repository *r,
if ((p->object.flags & flags) == flags)
continue;
if (repo_parse_commit(r, p)) {
clear_nonstale_queue(&queue);
clear_prio_queue(&state.queue);
commit_list_free(*result);
*result = NULL;
/*
@@ -184,12 +260,13 @@ static int paint_down_to_common(struct repository *r,
return error(_("could not parse commit %s"),
oid_to_hex(&p->object.oid));
}
p->object.flags |= flags;
nonstale_queue_put_dedup(&queue, p);
paint_queue_put(&state, p, flags);
}
}
clear_nonstale_queue(&queue);
clear_prio_queue(&state.queue);
trace2_data_intmax("paint_down_to_common", r,
"steps", steps);
commit_list_sort_by_date(result);
return 0;
}