mirror of
https://github.com/git/git.git
synced 2026-08-09 09:34:14 +00:00
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:
161
commit-reach.c
161
commit-reach.c
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user