Skip to content

Commit 28bca5c

Browse files
committed
commit-reach: remove get_reachable_subset()
get_reachable_subset() and tips_reachable_from_bases() both answer the same reachability question but use different traversal strategies: priority queue vs depth-first search. Consolidate them into tips_reachable_from_bases() with a mode parameter to select between DFS and PQ traversal, preserving the preferred strategy for each caller. This works cleanly because prio_queue already supports LIFO mode (when compare is NULL), so a single prio_queue acts as either a stack or a heap depending on the mode. The new DFS is not identical to the original: it queues all eligible parents at once rather than exploring one parent at a time. This means that when the generation floor is raised after finding a target, already-queued parents are not retroactively pruned. To compensate, commits popped below the current floor are skipped without exploring their subtrees. The flag in remote.c changes from 1 (bit 0) to TMP_MARK (bit 4) because tips_reachable_from_bases() uses SEEN (bit 0) internally. TMP_MARK is already used for deduplication earlier in the same function and is cleared before the reachability check. Signed-off-by: Kristofer Karlsson <krka@spotify.com>
1 parent 1ff279f commit 28bca5c

6 files changed

Lines changed: 74 additions & 152 deletions

File tree

commit-reach.c

Lines changed: 23 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,79 +1013,6 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
10131013
return result;
10141014
}
10151015

1016-
struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from,
1017-
struct commit **to, size_t nr_to,
1018-
unsigned int reachable_flag)
1019-
{
1020-
struct commit **item;
1021-
struct commit *current;
1022-
struct commit_list *found_commits = NULL;
1023-
struct commit **to_last = to + nr_to;
1024-
struct commit **from_last = from + nr_from;
1025-
timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
1026-
int num_to_find = 0;
1027-
1028-
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
1029-
1030-
for (item = to; item < to_last; item++) {
1031-
timestamp_t generation;
1032-
struct commit *c = *item;
1033-
1034-
repo_parse_commit(the_repository, c);
1035-
generation = commit_graph_generation(c);
1036-
if (generation < min_generation)
1037-
min_generation = generation;
1038-
1039-
if (!(c->object.flags & PARENT1)) {
1040-
c->object.flags |= PARENT1;
1041-
num_to_find++;
1042-
}
1043-
}
1044-
1045-
for (item = from; item < from_last; item++) {
1046-
struct commit *c = *item;
1047-
if (!(c->object.flags & PARENT2)) {
1048-
c->object.flags |= PARENT2;
1049-
repo_parse_commit(the_repository, c);
1050-
1051-
prio_queue_put(&queue, *item);
1052-
}
1053-
}
1054-
1055-
while (num_to_find && (current = prio_queue_get(&queue)) != NULL) {
1056-
struct commit_list *parents;
1057-
1058-
if (current->object.flags & PARENT1) {
1059-
current->object.flags &= ~PARENT1;
1060-
current->object.flags |= reachable_flag;
1061-
commit_list_insert(current, &found_commits);
1062-
num_to_find--;
1063-
}
1064-
1065-
for (parents = current->parents; parents; parents = parents->next) {
1066-
struct commit *p = parents->item;
1067-
1068-
repo_parse_commit(the_repository, p);
1069-
1070-
if (commit_graph_generation(p) < min_generation)
1071-
continue;
1072-
1073-
if (p->object.flags & PARENT2)
1074-
continue;
1075-
1076-
p->object.flags |= PARENT2;
1077-
prio_queue_put(&queue, p);
1078-
}
1079-
}
1080-
1081-
clear_prio_queue(&queue);
1082-
1083-
clear_commit_marks_many(nr_to, to, PARENT1);
1084-
clear_commit_marks_many(nr_from, from, PARENT2);
1085-
1086-
return found_commits;
1087-
}
1088-
10891016
define_commit_slab(bit_arrays, struct bitmap *);
10901017
static struct bit_arrays bit_arrays;
10911018

@@ -1212,22 +1139,27 @@ static int compare_commit_and_index_by_generation(const void *va, const void *vb
12121139
void tips_reachable_from_bases(struct repository *r,
12131140
struct commit_list *bases,
12141141
struct commit **tips, size_t tips_nr,
1215-
int mark)
1142+
int mark, enum tips_reachable_mode mode)
12161143
{
12171144
struct commit_and_index *commits;
1145+
struct commit_list *p;
1146+
struct commit *c;
1147+
struct commit_stack parents = COMMIT_STACK_INIT;
12181148
size_t min_generation_index = 0;
12191149
timestamp_t min_generation;
1220-
struct commit_list *stack = NULL;
1150+
struct prio_queue queue = { NULL };
12211151

12221152
if (!bases || !tips || !tips_nr)
12231153
return;
12241154

12251155
/*
1226-
* Do a depth-first search starting at 'bases' to search for the
1227-
* tips. Stop at the lowest (un-found) generation number. When
1228-
* finding the lowest commit, increase the minimum generation
1229-
* number to the next lowest (un-found) generation number.
1156+
* Search starting at 'bases' looking for the tips. Stop at the
1157+
* lowest un-found generation number, raising the floor as tips
1158+
* are found. Use DFS by default; with TIPS_REACHABLE_PQ,
1159+
* use a priority queue ordered by generation then commit date.
12301160
*/
1161+
if (mode == TIPS_REACHABLE_PQ)
1162+
queue.compare = compare_commits_by_gen_then_commit_date;
12311163

12321164
CALLOC_ARRAY(commits, tips_nr);
12331165

@@ -1245,14 +1177,15 @@ void tips_reachable_from_bases(struct repository *r,
12451177

12461178
while (bases) {
12471179
repo_parse_commit(r, bases->item);
1248-
commit_list_insert(bases->item, &stack);
1180+
bases->item->object.flags |= SEEN;
1181+
prio_queue_put(&queue, bases->item);
12491182
bases = bases->next;
12501183
}
12511184

1252-
while (stack) {
1253-
int explored_all_parents = 1;
1254-
struct commit_list *p;
1255-
struct commit *c = stack->item;
1185+
while ((c = prio_queue_get(&queue))) {
1186+
/* Skip if below the current generation floor. */
1187+
if (commit_graph_generation(c) < min_generation)
1188+
continue;
12561189

12571190
/* Does it match any of our tips? */
12581191
{
@@ -1275,34 +1208,27 @@ void tips_reachable_from_bases(struct repository *r,
12751208
}
12761209
}
12771210

1211+
/* Add parents in reverse so LIFO explores first-parent first. */
12781212
for (p = c->parents; p; p = p->next) {
12791213
repo_parse_commit(r, p->item);
1280-
1281-
/* Have we already explored this parent? */
12821214
if (p->item->object.flags & SEEN)
12831215
continue;
1284-
1285-
/* Is it below the current minimum generation? */
12861216
if (commit_graph_generation(p->item) < min_generation)
12871217
continue;
1288-
1289-
/* Ok, we will explore from here on. */
12901218
p->item->object.flags |= SEEN;
1291-
explored_all_parents = 0;
1292-
commit_list_insert(p->item, &stack);
1293-
break;
1219+
commit_stack_push(&parents, p->item);
12941220
}
1295-
1296-
if (explored_all_parents)
1297-
pop_commit(&stack);
1221+
while ((c = commit_stack_pop(&parents)))
1222+
prio_queue_put(&queue, c);
12981223
}
12991224

13001225
done:
13011226
for (size_t i = 0; i < tips_nr; i++)
13021227
commits[i].commit->object.flags &= ~RESULT;
13031228
free(commits);
13041229
repo_clear_commit_marks(r, SEEN);
1305-
commit_list_free(stack);
1230+
clear_prio_queue(&queue);
1231+
commit_stack_clear(&parents);
13061232
}
13071233

13081234
/*

commit-reach.h

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,6 @@ int can_all_from_reach_with_flag(struct object_array *from,
9696
int can_all_from_reach(struct commit_list *from, struct commit_list *to,
9797
int commit_date_cutoff);
9898

99-
100-
/*
101-
* Return a list of commits containing the commits in the 'to' array
102-
* that are reachable from at least one commit in the 'from' array.
103-
* Also add the given 'flag' to each of the commits in the returned list.
104-
*
105-
* This method uses the PARENT1 and PARENT2 flags during its operation,
106-
* so be sure these flags are not set before calling the method.
107-
*/
108-
struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from,
109-
struct commit **to, size_t nr_to,
110-
unsigned int reachable_flag);
111-
11299
struct ahead_behind_count {
113100
/**
114101
* As input, the *_index members indicate which positions in
@@ -144,10 +131,14 @@ void ahead_behind(struct repository *r,
144131
* For all tip commits, add 'mark' to their flags if and only if they
145132
* are reachable from one of the commits in 'bases'.
146133
*/
134+
enum tips_reachable_mode {
135+
TIPS_REACHABLE_DFS,
136+
TIPS_REACHABLE_PQ,
137+
};
147138
void tips_reachable_from_bases(struct repository *r,
148139
struct commit_list *bases,
149140
struct commit **tips, size_t tips_nr,
150-
int mark);
141+
int mark, enum tips_reachable_mode mode);
151142

152143
/*
153144
* Given a 'tip' commit and a list potential 'bases', return the index 'i' that

ref-filter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3157,7 +3157,7 @@ static void reach_filter(struct ref_array *array,
31573157
tips_reachable_from_bases(the_repository,
31583158
*check_reachable,
31593159
to_clear, array->nr,
3160-
UNINTERESTING);
3160+
UNINTERESTING, TIPS_REACHABLE_DFS);
31613161

31623162
old_nr = array->nr;
31633163
array->nr = 0;

remote.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,9 +1459,8 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
14591459
* sent to the other side.
14601460
*/
14611461
if (sent_tips.nr) {
1462-
const int reachable_flag = 1;
1463-
struct commit_list *found_commits;
14641462
struct commit_stack src_commits = COMMIT_STACK_INIT;
1463+
struct commit_list *bases = NULL;
14651464

14661465
for_each_string_list_item(item, &src_tag) {
14671466
struct ref *ref = item->util;
@@ -1479,11 +1478,13 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
14791478
commit_stack_push(&src_commits, commit);
14801479
}
14811480

1482-
found_commits = get_reachable_subset(sent_tips.items,
1483-
sent_tips.nr,
1484-
src_commits.items,
1485-
src_commits.nr,
1486-
reachable_flag);
1481+
for (size_t i = 0; i < sent_tips.nr; i++)
1482+
commit_list_insert(sent_tips.items[i], &bases);
1483+
tips_reachable_from_bases(the_repository,
1484+
bases, src_commits.items,
1485+
src_commits.nr, TMP_MARK,
1486+
TIPS_REACHABLE_PQ);
1487+
commit_list_free(bases);
14871488

14881489
for_each_string_list_item(item, &src_tag) {
14891490
struct ref *dst_ref;
@@ -1503,7 +1504,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
15031504
* Is this tag, which they do not have, reachable from
15041505
* any of the commits we are sending?
15051506
*/
1506-
if (!(commit->object.flags & reachable_flag))
1507+
if (!(commit->object.flags & TMP_MARK))
15071508
continue;
15081509

15091510
/* Add it in */
@@ -1513,9 +1514,8 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds
15131514
}
15141515

15151516
clear_commit_marks_many(src_commits.nr, src_commits.items,
1516-
reachable_flag);
1517+
TMP_MARK);
15171518
commit_stack_clear(&src_commits);
1518-
commit_list_free(found_commits);
15191519
}
15201520

15211521
string_list_clear(&src_tag, 0);

t/helper/test-reach.c

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "hex.h"
88
#include "object-name.h"
99
#include "ref-filter.h"
10+
#include "revision.h"
1011
#include "setup.h"
1112
#include "string-list.h"
1213
#include "tag.h"
@@ -149,30 +150,31 @@ int cmd__reach(int ac, const char **av)
149150

150151
printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache));
151152
clear_contains_cache(&cache);
152-
} else if (!strcmp(av[1], "get_reachable_subset")) {
153-
const int reachable_flag = 1;
154-
int count = 0;
155-
struct commit_list *current;
156-
struct commit_list *list = get_reachable_subset(X_stack.items, X_stack.nr,
157-
Y_stack.items, Y_stack.nr,
158-
reachable_flag);
159-
printf("get_reachable_subset(X,Y)\n");
160-
for (current = list; current; current = current->next) {
161-
if (!(list->item->object.flags & reachable_flag))
162-
die(_("commit %s is not marked reachable"),
163-
oid_to_hex(&list->item->object.oid));
164-
count++;
165-
}
153+
} else if (!strcmp(av[1], "tips_reachable_from_bases") ||
154+
!strcmp(av[1], "tips_reachable_from_bases_pq")) {
155+
enum tips_reachable_mode mode =
156+
!strcmp(av[1], "tips_reachable_from_bases_pq")
157+
? TIPS_REACHABLE_PQ : TIPS_REACHABLE_DFS;
158+
struct commit_list *bases = NULL;
159+
struct commit_list *result = NULL;
160+
161+
for (size_t i = 0; i < X_stack.nr; i++)
162+
commit_list_insert(X_stack.items[i], &bases);
163+
tips_reachable_from_bases(the_repository,
164+
bases, Y_stack.items,
165+
Y_stack.nr, TMP_MARK,
166+
mode);
167+
commit_list_free(bases);
168+
169+
printf("tips_reachable_from_bases(X,Y)\n");
166170
for (size_t i = 0; i < Y_stack.nr; i++) {
167-
if (Y_stack.items[i]->object.flags & reachable_flag)
168-
count--;
171+
if (Y_stack.items[i]->object.flags & TMP_MARK)
172+
commit_list_insert(Y_stack.items[i], &result);
169173
}
174+
print_sorted_commit_ids(result);
170175

171-
if (count < 0)
172-
die(_("too many commits marked reachable"));
173-
174-
print_sorted_commit_ids(list);
175-
commit_list_free(list);
176+
clear_commit_marks_many(Y_stack.nr, Y_stack.items, TMP_MARK);
177+
commit_list_free(result);
176178
}
177179

178180
object_array_clear(&X_obj);

t/t6600-test-reach.sh

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ test_expect_success 'rev-list: symmetric difference topo-order' '
391391
run_all_modes git rev-list --topo-order commit-3-8...commit-6-6
392392
'
393393

394-
test_expect_success 'get_reachable_subset:all' '
394+
test_expect_success 'tips_reachable_from_bases:all' '
395395
cat >input <<-\EOF &&
396396
X:commit-9-1
397397
X:commit-8-3
@@ -403,15 +403,16 @@ test_expect_success 'get_reachable_subset:all' '
403403
Y:commit-5-6
404404
EOF
405405
(
406-
echo "get_reachable_subset(X,Y)" &&
406+
echo "tips_reachable_from_bases(X,Y)" &&
407407
git rev-parse commit-3-3 \
408408
commit-1-7 \
409409
commit-5-6 | sort
410410
) >expect &&
411-
test_all_modes get_reachable_subset
411+
test_all_modes tips_reachable_from_bases &&
412+
test_all_modes tips_reachable_from_bases_pq
412413
'
413414

414-
test_expect_success 'get_reachable_subset:some' '
415+
test_expect_success 'tips_reachable_from_bases:some' '
415416
cat >input <<-\EOF &&
416417
X:commit-9-1
417418
X:commit-8-3
@@ -422,14 +423,15 @@ test_expect_success 'get_reachable_subset:some' '
422423
Y:commit-5-6
423424
EOF
424425
(
425-
echo "get_reachable_subset(X,Y)" &&
426+
echo "tips_reachable_from_bases(X,Y)" &&
426427
git rev-parse commit-3-3 \
427428
commit-1-7 | sort
428429
) >expect &&
429-
test_all_modes get_reachable_subset
430+
test_all_modes tips_reachable_from_bases &&
431+
test_all_modes tips_reachable_from_bases_pq
430432
'
431433

432-
test_expect_success 'get_reachable_subset:none' '
434+
test_expect_success 'tips_reachable_from_bases:none' '
433435
cat >input <<-\EOF &&
434436
X:commit-9-1
435437
X:commit-8-3
@@ -439,8 +441,9 @@ test_expect_success 'get_reachable_subset:none' '
439441
Y:commit-7-6
440442
Y:commit-2-8
441443
EOF
442-
echo "get_reachable_subset(X,Y)" >expect &&
443-
test_all_modes get_reachable_subset
444+
echo "tips_reachable_from_bases(X,Y)" >expect &&
445+
test_all_modes tips_reachable_from_bases &&
446+
test_all_modes tips_reachable_from_bases_pq
444447
'
445448

446449
test_expect_success 'for-each-ref ahead-behind:linear' '

0 commit comments

Comments
 (0)