diff --git a/builtin/tag.c b/builtin/tag.c index d51c2e33495295..9f34d948d42e97 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -71,7 +71,6 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, if (verify_ref_format(format)) die(_("unable to parse format string")); - filter->with_commit_tag_algo = 1; filter_and_format_refs(filter, FILTER_REFS_TAGS, sorting, format); free(to_free); diff --git a/commit-reach.c b/commit-reach.c index 9b3ea46d6f2824..a8d52c8ef0954d 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -6,7 +6,6 @@ #include "decorate.h" #include "hex.h" #include "prio-queue.h" -#include "ref-filter.h" #include "revision.h" #include "tag.h" #include "commit-reach.h" @@ -520,12 +519,7 @@ int repo_is_descendant_of(struct repository *r, return 1; if (generation_numbers_enabled(r)) { - struct commit_list *from_list = NULL; - int result; - commit_list_insert(commit, &from_list); - result = can_all_from_reach(from_list, with_commit, 0); - commit_list_free(from_list); - return result; + return find_reachable_list(r, &commit, 1, with_commit, 0); } else { while (with_commit) { struct commit *other; @@ -571,6 +565,16 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, if (generation > max_generation) return ret; + if (generation_numbers_enabled(r)) { + ret = find_reachable(r, reference, nr_reference, + &commit, 1, 0); + if (ret > 0) + return 1; + if (ret < 0 && !ignore_missing_commits) + return ret; + return 0; + } + if (paint_down_to_common(r, commit, nr_reference, reference, generation, mb_flags, &bases)) @@ -683,126 +687,14 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid) return ret; } -/* - * Mimicking the real stack, this stack lives on the heap, avoiding stack - * overflows. - * - * At each recursion step, the stack items points to the commits whose - * ancestors are to be inspected. - */ -struct contains_stack { - int nr, alloc; - struct contains_stack_entry { - struct commit *commit; - struct commit_list *parents; - } *contains_stack; -}; - -static int in_commit_list(const struct commit_list *want, struct commit *c) -{ - for (; want; want = want->next) - if (oideq(&want->item->object.oid, &c->object.oid)) - return 1; - return 0; -} - -/* - * Test whether the candidate is contained in the list. - * Do not recurse to find out, though, but return -1 if inconclusive. - */ -static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want, - struct contains_cache *cache, - timestamp_t cutoff) -{ - enum contains_result *cached = contains_cache_at(cache, candidate); - - /* If we already have the answer cached, return that. */ - if (*cached) - return *cached; - - /* or are we it? */ - if (in_commit_list(want, candidate)) { - *cached = CONTAINS_YES; - return CONTAINS_YES; - } - - /* Otherwise, we don't know; prepare to recurse */ - parse_commit_or_die(candidate); - - if (commit_graph_generation(candidate) < cutoff) - return CONTAINS_NO; - - return CONTAINS_UNKNOWN; -} - -static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack) -{ - ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc); - contains_stack->contains_stack[contains_stack->nr].commit = candidate; - contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents; -} - -static enum contains_result contains_tag_algo(struct commit *candidate, - const struct commit_list *want, - struct contains_cache *cache) -{ - struct contains_stack contains_stack = { 0, 0, NULL }; - enum contains_result result; - timestamp_t cutoff = GENERATION_NUMBER_INFINITY; - const struct commit_list *p; - - for (p = want; p; p = p->next) { - timestamp_t generation; - struct commit *c = p->item; - load_commit_graph_info(the_repository, c); - generation = commit_graph_generation(c); - if (generation < cutoff) - cutoff = generation; - } - - result = contains_test(candidate, want, cache, cutoff); - if (result != CONTAINS_UNKNOWN) - return result; - - push_to_contains_stack(candidate, &contains_stack); - while (contains_stack.nr) { - struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1]; - struct commit *commit = entry->commit; - struct commit_list *parents = entry->parents; - - if (!parents) { - *contains_cache_at(cache, commit) = CONTAINS_NO; - contains_stack.nr--; - } - /* - * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful yes/no. - */ - else switch (contains_test(parents->item, want, cache, cutoff)) { - case CONTAINS_YES: - *contains_cache_at(cache, commit) = CONTAINS_YES; - contains_stack.nr--; - break; - case CONTAINS_NO: - entry->parents = parents->next; - break; - case CONTAINS_UNKNOWN: - push_to_contains_stack(parents->item, &contains_stack); - break; - } - } - free(contains_stack.contains_stack); - return contains_test(candidate, want, cache, cutoff); -} - -int commit_contains(struct ref_filter *filter, struct commit *commit, - struct commit_list *list, struct contains_cache *cache) -{ - if (filter->with_commit_tag_algo) - return contains_tag_algo(commit, list, cache) == CONTAINS_YES; - return repo_is_descendant_of(the_repository, commit, list); -} +static int find_reachable_core(struct repository *r, + struct commit **from, size_t from_nr, + unsigned int target_flag, + unsigned int visited_flag, + timestamp_t min_generation, + timestamp_t min_commit_date, + int fast_fail, + unsigned int mark); int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, @@ -846,54 +738,12 @@ int can_all_from_reach_with_flag(struct object_array *from, nr_commits++; } - QSORT(list, nr_commits, compare_commits_by_gen); - - for (i = 0; i < nr_commits; i++) { - /* DFS from list[i] */ - struct commit_list *stack = NULL; - - list[i]->object.flags |= assign_flag; - commit_list_insert(list[i], &stack); - - while (stack) { - struct commit_list *parent; - - if (stack->item->object.flags & (with_flag | RESULT)) { - pop_commit(&stack); - if (stack) - stack->item->object.flags |= RESULT; - continue; - } - - for (parent = stack->item->parents; parent; parent = parent->next) { - if (parent->item->object.flags & (with_flag | RESULT)) - stack->item->object.flags |= RESULT; - - if (!(parent->item->object.flags & assign_flag)) { - parent->item->object.flags |= assign_flag; - - if (repo_parse_commit(the_repository, parent->item) || - parent->item->date < min_commit_date || - commit_graph_generation(parent->item) < min_generation) - continue; - - commit_list_insert(parent->item, &stack); - break; - } - } - - if (!parent) - pop_commit(&stack); - } - - if (!(list[i]->object.flags & (with_flag | RESULT))) { - result = 0; - goto cleanup; - } - } + result = find_reachable_core(the_repository, list, nr_commits, + with_flag, assign_flag, + min_generation, min_commit_date, + 1, 0) == (int)nr_commits; cleanup: - clear_commit_marks_many(nr_commits, list, RESULT | assign_flag); free(list); for (i = 0; i < from->nr; i++) { @@ -906,64 +756,6 @@ int can_all_from_reach_with_flag(struct object_array *from, return result; } -int can_all_from_reach(struct commit_list *from, struct commit_list *to, - int cutoff_by_min_date) -{ - struct object_array from_objs = OBJECT_ARRAY_INIT; - struct commit_list *from_iter = from, *to_iter = to; - int result; - timestamp_t min_commit_date = cutoff_by_min_date ? from->item->date : 0; - timestamp_t min_generation = GENERATION_NUMBER_INFINITY; - - while (from_iter) { - add_object_array(&from_iter->item->object, NULL, &from_objs); - - if (!repo_parse_commit(the_repository, from_iter->item)) { - timestamp_t generation; - if (from_iter->item->date < min_commit_date) - min_commit_date = from_iter->item->date; - - generation = commit_graph_generation(from_iter->item); - if (generation < min_generation) - min_generation = generation; - } - - from_iter = from_iter->next; - } - - while (to_iter) { - if (!repo_parse_commit(the_repository, to_iter->item)) { - timestamp_t generation; - if (to_iter->item->date < min_commit_date) - min_commit_date = to_iter->item->date; - - generation = commit_graph_generation(to_iter->item); - if (generation < min_generation) - min_generation = generation; - } - - to_iter->item->object.flags |= PARENT2; - - to_iter = to_iter->next; - } - - result = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1, - min_commit_date, min_generation); - - while (from) { - clear_commit_marks(from->item, PARENT1); - from = from->next; - } - - while (to) { - clear_commit_marks(to->item, PARENT2); - to = to->next; - } - - object_array_clear(&from_objs); - return result; -} - struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from, struct commit **to, size_t nr_to, unsigned int reachable_flag) @@ -1141,6 +933,159 @@ void ahead_behind(struct repository *r, clear_prio_queue(&queue); } +static int find_reachable_one(struct repository *r, struct commit *from, + unsigned int target_flag, + unsigned int visited_flag, + timestamp_t min_generation, + timestamp_t min_commit_date) +{ + struct commit_list *stack = NULL; + + if (repo_parse_commit(r, from)) + return -1; + if (commit_graph_generation(from) < min_generation) + return 0; + if (from->object.flags & RESULT) + return 1; + + from->object.flags |= visited_flag; + commit_list_insert(from, &stack); + + while (stack) { + struct commit_list *parents; + + if (stack->item->object.flags & target_flag) + stack->item->object.flags |= RESULT; + + if (stack->item->object.flags & RESULT) { + pop_commit(&stack); + if (stack) + stack->item->object.flags |= RESULT; + continue; + } + + for (parents = stack->item->parents; parents; + parents = parents->next) { + struct commit *p = parents->item; + + if (p->object.flags & (target_flag | RESULT)) + stack->item->object.flags |= RESULT; + + if (!(p->object.flags & visited_flag)) { + p->object.flags |= visited_flag; + + if (repo_parse_commit(r, p)) { + while (stack) + pop_commit(&stack); + return -1; + } + if (commit_graph_generation(p) < min_generation || + p->date < min_commit_date) + continue; + + commit_list_insert(p, &stack); + break; + } + } + + if (!parents) { + int has_result = stack->item->object.flags & RESULT; + pop_commit(&stack); + if (has_result && stack) + stack->item->object.flags |= RESULT; + } + } + + return from->object.flags & RESULT ? 1 : 0; +} + +/* Clears visited_flag | RESULT; caller must clear target_flag. */ +static int find_reachable_core(struct repository *r, + struct commit **from, size_t from_nr, + unsigned int target_flag, + unsigned int visited_flag, + timestamp_t min_generation, + timestamp_t min_commit_date, + int fast_fail, + unsigned int mark) +{ + struct commit **sorted; + int found = 0; + + DUP_ARRAY(sorted, from, from_nr); + QSORT(sorted, from_nr, compare_commits_by_gen); + + for (size_t i = 0; i < from_nr; i++) { + int result = find_reachable_one(r, sorted[i], target_flag, + visited_flag, min_generation, + min_commit_date); + if (result < 0) { + found = -1; + break; + } + if (result) { + sorted[i]->object.flags |= mark; + found++; + } else if (fast_fail) { + break; + } + } + + clear_commit_marks_many(from_nr, sorted, visited_flag | RESULT); + free(sorted); + + return found; +} + +int find_reachable(struct repository *r, + struct commit **from, size_t from_nr, + struct commit **to, size_t to_nr, + unsigned int mark) +{ + int found; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; + + if (!from_nr || !to_nr) + return 0; + + for (size_t i = 0; i < to_nr; i++) { + timestamp_t gen; + if (repo_parse_commit(r, to[i])) + die(_("could not parse commit %s"), + oid_to_hex(&to[i]->object.oid)); + gen = commit_graph_generation(to[i]); + if (gen < min_generation) + min_generation = gen; + to[i]->object.flags |= PARENT2; + } + + found = find_reachable_core(r, from, from_nr, PARENT2, PARENT1, + min_generation, 0, 0, mark); + + for (size_t i = 0; i < to_nr; i++) + to[i]->object.flags &= ~PARENT2; + + return found; +} + +int find_reachable_list(struct repository *r, + struct commit **from, size_t from_nr, + struct commit_list *to, + unsigned int mark) +{ + size_t to_nr = commit_list_count(to); + struct commit **to_array; + int ret; + + ALLOC_ARRAY(to_array, to_nr); + for (size_t i = 0; i < to_nr; i++, to = to->next) + to_array[i] = to->item; + + ret = find_reachable(r, from, from_nr, to_array, to_nr, mark); + free(to_array); + return ret; +} + struct commit_and_index { struct commit *commit; timestamp_t generation; diff --git a/commit-reach.h b/commit-reach.h index 3f3a563d8a5dd1..ae5ad8cb8d4249 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -5,7 +5,6 @@ #include "commit-slab.h" struct commit_list; -struct ref_filter; struct object_id; struct object_array; @@ -66,21 +65,6 @@ void reduce_heads_replace(struct commit_list **heads); int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid); -/* - * Unknown has to be "0" here, because that's the default value for - * contains_cache slab entries that have not yet been assigned. - */ -enum contains_result { - CONTAINS_UNKNOWN = 0, - CONTAINS_NO, - CONTAINS_YES -}; - -define_commit_slab(contains_cache, enum contains_result); - -int commit_contains(struct ref_filter *filter, struct commit *commit, - struct commit_list *list, struct contains_cache *cache); - /* * Determine if every commit in 'from' can reach at least one commit * that is marked with 'with_flag'. As we traverse, use 'assign_flag' @@ -93,9 +77,6 @@ int can_all_from_reach_with_flag(struct object_array *from, unsigned int assign_flag, timestamp_t min_commit_date, timestamp_t min_generation); -int can_all_from_reach(struct commit_list *from, struct commit_list *to, - int commit_date_cutoff); - /* * Return a list of commits containing the commits in the 'to' array @@ -109,6 +90,26 @@ struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from, struct commit **to, size_t nr_to, unsigned int reachable_flag); +/* + * For each 'from' commit, check if it can reach any 'to' commit via + * parent edges. Set 'mark' on each reachable 'from' commit; pass 0 + * to skip marking. Returns the number of reachable 'from' commits, + * or -1 on error. + * + * Temporarily modifies PARENT1, PARENT2, and RESULT on visited + * commits; all three are cleared before returning. Callers must + * not rely on these flags across calls. + */ +int find_reachable(struct repository *r, + struct commit **from, size_t from_nr, + struct commit **to, size_t to_nr, + unsigned int mark); + +int find_reachable_list(struct repository *r, + struct commit **from, size_t from_nr, + struct commit_list *to, + unsigned int mark); + struct ahead_behind_count { /** * As input, the *_index members indicate which positions in diff --git a/ref-filter.c b/ref-filter.c index 1da4c0e60df3fa..e79b443ec70d8d 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2989,14 +2989,6 @@ static struct ref_array_item *apply_ref_filter(const struct reference *ref, commit = lookup_commit_reference_gently(the_repository, ref->oid, 1); if (!commit) return NULL; - /* We perform the filtering for the '--contains' option... */ - if (filter->with_commit && - !commit_contains(filter, commit, filter->with_commit, &filter->internal.contains_cache)) - return NULL; - /* ...or for the `--no-contains' option */ - if (filter->no_commit && - commit_contains(filter, commit, filter->no_commit, &filter->internal.no_contains_cache)) - return NULL; } /* @@ -3136,6 +3128,37 @@ void ref_array_clear(struct ref_array *array) FREE_AND_NULL(array->counts); } +static void filter_by_reachability(struct ref_array *array, + struct commit_list *targets, + int keep_reachable) +{ + struct commit **tips; + size_t old_nr; + + if (!targets || !array->nr) + return; + + ALLOC_ARRAY(tips, array->nr); + for (size_t i = 0; i < array->nr; i++) + tips[i] = array->items[i]->commit; + + if (find_reachable_list(the_repository, tips, array->nr, + targets, UNINTERESTING) < 0) + die(_("could not check reachability")); + + old_nr = array->nr; + array->nr = 0; + for (size_t i = 0; i < old_nr; i++) { + if (!!(tips[i]->object.flags & UNINTERESTING) == keep_reachable) + array->items[array->nr++] = array->items[i]; + else + free_array_item(array->items[i]); + } + for (size_t i = 0; i < old_nr; i++) + tips[i]->object.flags &= ~UNINTERESTING; + free(tips); +} + #define EXCLUDE_REACHED 0 #define INCLUDE_REACHED 1 static void reach_filter(struct ref_array *array, @@ -3294,9 +3317,6 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for filter->kind = type & FILTER_REFS_KIND_MASK; - init_contains_cache(&filter->internal.contains_cache); - init_contains_cache(&filter->internal.no_contains_cache); - /* Simple per-ref filtering */ if (!filter->kind) die("filter_refs: invalid type"); @@ -3342,9 +3362,6 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for cb_data); - clear_contains_cache(&filter->internal.contains_cache); - clear_contains_cache(&filter->internal.no_contains_cache); - return ret; } @@ -3369,6 +3386,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int ret = do_filter_refs(filter, type, filter_one, &ref_cbdata); /* Filters that need revision walking */ + filter_by_reachability(array, filter->with_commit, 1); + filter_by_reachability(array, filter->no_commit, 0); reach_filter(array, &filter->reachable_from, INCLUDE_REACHED); reach_filter(array, &filter->unreachable_from, EXCLUDE_REACHED); @@ -3413,7 +3432,8 @@ static inline int can_do_iterative_format(struct ref_filter *filter, if (used_atom[i].atom_type == ATOM_ISBASE) return 0; } - return !(filter->reachable_from || filter->unreachable_from); + return !(filter->reachable_from || filter->unreachable_from || + filter->with_commit || filter->no_commit); } void filter_and_format_refs(struct ref_filter *filter, unsigned int type, diff --git a/ref-filter.h b/ref-filter.h index 120221b47fa30d..6c7f3e218fada6 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -73,19 +73,13 @@ struct ref_filter { struct commit_list *reachable_from; struct commit_list *unreachable_from; - unsigned int with_commit_tag_algo : 1, - match_as_path : 1, + unsigned int match_as_path : 1, ignore_case : 1, detached : 1; unsigned int kind, lines; int abbrev, verbose; - - struct { - struct contains_cache contains_cache; - struct contains_cache no_contains_cache; - } internal; }; struct ref_format { diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index 5d86a96c17e4e5..d3465ea33a6cbe 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c @@ -6,7 +6,6 @@ #include "gettext.h" #include "hex.h" #include "object-name.h" -#include "ref-filter.h" #include "setup.h" #include "string-list.h" #include "tag.h" @@ -127,7 +126,10 @@ int cmd__reach(int ac, const char **av) print_sorted_commit_ids(list); commit_list_free(list); } else if (!strcmp(av[1], "can_all_from_reach")) { - printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); + printf("%s(X,Y):%d\n", av[1], + find_reachable(r, X_stack.items, X_stack.nr, + Y_stack.items, Y_stack.nr, + 0) == (int)X_stack.nr); } else if (!strcmp(av[1], "can_all_from_reach_with_flag")) { struct commit_list *iter = Y; @@ -138,17 +140,8 @@ int cmd__reach(int ac, const char **av) printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0)); } else if (!strcmp(av[1], "commit_contains")) { - struct ref_filter filter = REF_FILTER_INIT; - struct contains_cache cache; - init_contains_cache(&cache); - - if (ac > 2 && !strcmp(av[2], "--tag")) - filter.with_commit_tag_algo = 1; - else - filter.with_commit_tag_algo = 0; - - printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache)); - clear_contains_cache(&cache); + printf("%s(_,A,X,_):%d\n", av[1], + repo_is_descendant_of(r, A, X)); } else if (!strcmp(av[1], "get_reachable_subset")) { const int reachable_flag = 1; int count = 0;