commit-reach: deduplicate queue entries in paint_down_to_common

paint_down_to_common() can enqueue the same commit multiple times when it is reached through different parents with different flag combinations. Add an ENQUEUED flag to track whether a commit is currently in the priority queue, and skip it if already present. Introduce prio_queue_put_dedup() and prio_queue_get_dedup() wrappers that manage the ENQUEUED flag on enqueue and dequeue. This change is performance-neutral on its own: the O(n) queue_has_nonstale() scan still dominates the per-iteration cost. However, the deduplication guarantee (each commit appears in the queue at most once) is a prerequisite for the next commit, which replaces that scan with O(1) tracking. Signed-off-by: Kristofer Karlsson <krka@spotify.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kristofer Karlsson committed May 25, 2026 at 14:28 UTC f767dae3e6c8359128d0ec83acd009751e92e419
2 files changed +23 -6
commit-reach.c
+22 -5
@@ -17,8 +17,9 @@
17 #define PARENT2 (1u<<17)
18 #define STALE (1u<<18)
19 #define RESULT (1u<<19)
20 +#define ENQUEUED (1u<<20)
21
21 -static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
22 +static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT | ENQUEUED);
23
24 static int compare_commits_by_gen(const void *_a, const void *_b)
25 {
@@ -39,6 +40,22 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
40 return 0;
41 }
42
43 +static void prio_queue_put_dedup(struct prio_queue *queue, struct commit *c)
44 +{
45 + if (c->object.flags & ENQUEUED)
46 + return;
47 + c->object.flags |= ENQUEUED;
48 + prio_queue_put(queue, c);
49 +}
50 +
51 +static struct commit *prio_queue_get_dedup(struct prio_queue *queue)
52 +{
53 + struct commit *commit = prio_queue_get(queue);
54 + if (commit)
55 + commit->object.flags &= ~ENQUEUED;
56 + return commit;
57 +}
58 +
59 static int queue_has_nonstale(struct prio_queue *queue)
60 {
61 for (size_t i = 0; i < queue->nr; i++) {
@@ -70,15 +87,15 @@ static int paint_down_to_common(struct repository *r,
87 commit_list_append(one, result);
88 return 0;
89 }
73 - prio_queue_put(&queue, one);
90 + prio_queue_put_dedup(&queue, one);
91
92 for (i = 0; i < n; i++) {
93 twos[i]->object.flags |= PARENT2;
77 - prio_queue_put(&queue, twos[i]);
94 + prio_queue_put_dedup(&queue, twos[i]);
95 }
96
97 while (queue_has_nonstale(&queue)) {
81 - struct commit *commit = prio_queue_get(&queue);
98 + struct commit *commit = prio_queue_get_dedup(&queue);
99 struct commit_list *parents;
100 int flags;
101 timestamp_t generation = commit_graph_generation(commit);
@@ -124,7 +141,7 @@ static int paint_down_to_common(struct repository *r,
141 oid_to_hex(&p->object.oid));
142 }
143 p->object.flags |= flags;
127 - prio_queue_put(&queue, p);
144 + prio_queue_put_dedup(&queue, p);
145 }
146 }
147
object.h
+1 -1
@@ -75,7 +75,7 @@ void object_array_init(struct object_array *array);
75 * bundle.c: 16
76 * http-push.c: 11-----14
77 * commit-graph.c: 15
78 - * commit-reach.c: 16-----19
78 + * commit-reach.c: 16-------20
79 * builtin/last-modified.c: 1617
80 * object-name.c: 20
81 * list-objects-filter.c: 21