Skip to content

Commit a60b701

Browse files
committed
commit-reach: use can_all_from_reach
The is_descendant_of method previously used in_merge_bases() to check if the commit can reach any of the commits in the provided list. This had two performance problems: 1. The performance is quadratic in worst-case. 2. A single in_merge_bases() call requires walking beyond the target commit in order to find the full set of boundary commits that may be merge-bases. The can_all_from_reach method avoids this quadratic behavior and can limit the search beyond the target commits using generation numbers. It requires a small prototype adjustment to stop using commit-date as a cutoff, as that optimization is no longer appropriate here. Since in_merge_bases() uses paint_down_to_common(), is_descendant_of() naturally found cutoffs to avoid walking the entire commit graph. Since we want to always return the correct result, we cannot use the min_commit_date cutoff in can_all_from_reach. We then rely on generation numbers to provide the cutoff. Since not all repos will have a commit-graph file, nor will we always have generation numbers computed for a commit-graph file, create a new method, generation_numbers_enabled(), that checks for a commit-graph file and sees if the first commit in the file has a non-zero generation number. In the case that we do not have generation numbers, use the old logic for is_descendant_of(). Performance was meausured on a copy of the Linux repository using the 'test-tool reach is_descendant_of' command using this input: A:v4.9 X:v4.10 X:v4.11 X:v4.12 X:v4.13 X:v4.14 X:v4.15 X:v4.16 X:v4.17 X.v3.0 Note that this input is tailored to demonstrate the quadratic nature of the previous method, as it will compute merge-bases for v4.9 versus all of the later versions before checking against v4.1. Before: 0.26 s After: 0.21 s Since we previously used the is_descendant_of method in the ref_newer method, we also measured performance there using 'test-tool reach ref_newer' with this input: A:v4.9 B:v3.19 Before: 0.10 s After: 0.08 s By adding a new commit with parent v3.19, we test the non-reachable case of ref_newer: Before: 0.09 s After: 0.08 s Signed-off-by: Derrick Stolee <[email protected]>
1 parent 816821e commit a60b701

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

commit-graph.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,24 @@ static int prepare_commit_graph(struct repository *r)
233233
return !!r->objects->commit_graph;
234234
}
235235

236+
int generation_numbers_enabled(struct repository *r)
237+
{
238+
uint32_t first_generation;
239+
struct commit_graph *g;
240+
if (!prepare_commit_graph(r))
241+
return 0;
242+
243+
g = r->objects->commit_graph;
244+
245+
if (!g->num_commits)
246+
return 0;
247+
248+
first_generation = get_be32(g->chunk_commit_data +
249+
g->hash_len + 8) >> 2;
250+
251+
return !!first_generation;
252+
}
253+
236254
static void close_commit_graph(void)
237255
{
238256
free_commit_graph(the_repository->objects->commit_graph);

commit-graph.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ struct commit_graph {
5151

5252
struct commit_graph *load_commit_graph_one(const char *graph_file);
5353

54+
/*
55+
* Return 1 if and only if the repository has a commit-graph
56+
* file and generation numbers are computed in that file.
57+
*/
58+
int generation_numbers_enabled(struct repository *r);
59+
5460
void write_commit_graph_reachable(const char *obj_dir, int append);
5561
void write_commit_graph(const char *obj_dir,
5662
struct string_list *pack_indexes,

commit-reach.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,25 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
276276
{
277277
if (!with_commit)
278278
return 1;
279-
while (with_commit) {
280-
struct commit *other;
281279

282-
other = with_commit->item;
283-
with_commit = with_commit->next;
284-
if (in_merge_bases(other, commit))
285-
return 1;
280+
if (generation_numbers_enabled(the_repository)) {
281+
struct commit_list *from_list = NULL;
282+
int result;
283+
commit_list_insert(commit, &from_list);
284+
result = can_all_from_reach(from_list, with_commit, 0);
285+
free_commit_list(from_list);
286+
return result;
287+
} else {
288+
while (with_commit) {
289+
struct commit *other;
290+
291+
other = with_commit->item;
292+
with_commit = with_commit->next;
293+
if (in_merge_bases(other, commit))
294+
return 1;
295+
}
296+
return 0;
286297
}
287-
return 0;
288298
}
289299

290300
/*

0 commit comments

Comments
 (0)