diff options
Diffstat (limited to 'commit-reach.c')
| -rw-r--r-- | commit-reach.c | 406 |
1 files changed, 301 insertions, 105 deletions
diff --git a/commit-reach.c b/commit-reach.c index ecc913fc99..a339e41aa4 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -1,3 +1,5 @@ +#define USE_THE_REPOSITORY_VARIABLE + #include "git-compat-util.h" #include "commit.h" #include "commit-graph.h" @@ -39,8 +41,7 @@ static int compare_commits_by_gen(const void *_a, const void *_b) static int queue_has_nonstale(struct prio_queue *queue) { - int i; - for (i = 0; i < queue->nr; i++) { + for (size_t i = 0; i < queue->nr; i++) { struct commit *commit = queue->array[i].data; if (!(commit->object.flags & STALE)) return 1; @@ -49,13 +50,14 @@ static int queue_has_nonstale(struct prio_queue *queue) } /* all input commits in one and twos[] must have been parsed! */ -static struct commit_list *paint_down_to_common(struct repository *r, - struct commit *one, int n, - struct commit **twos, - timestamp_t min_generation) +static int paint_down_to_common(struct repository *r, + struct commit *one, int n, + struct commit **twos, + timestamp_t min_generation, + int ignore_missing_commits, + struct commit_list **result) { struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; - struct commit_list *result = NULL; int i; timestamp_t last_gen = GENERATION_NUMBER_INFINITY; @@ -64,8 +66,8 @@ static struct commit_list *paint_down_to_common(struct repository *r, one->object.flags |= PARENT1; if (!n) { - commit_list_append(one, &result); - return result; + commit_list_append(one, result); + return 0; } prio_queue_put(&queue, one); @@ -93,7 +95,7 @@ static struct commit_list *paint_down_to_common(struct repository *r, if (flags == (PARENT1 | PARENT2)) { if (!(commit->object.flags & RESULT)) { commit->object.flags |= RESULT; - commit_list_insert_by_date(commit, &result); + commit_list_insert_by_date(commit, result); } /* Mark parents of a found merge stale */ flags |= STALE; @@ -104,67 +106,97 @@ static struct commit_list *paint_down_to_common(struct repository *r, parents = parents->next; if ((p->object.flags & flags) == flags) continue; - if (repo_parse_commit(r, p)) - return NULL; + if (repo_parse_commit(r, p)) { + clear_prio_queue(&queue); + free_commit_list(*result); + *result = NULL; + /* + * At this stage, we know that the commit is + * missing: `repo_parse_commit()` uses + * `OBJECT_INFO_DIE_IF_CORRUPT` and therefore + * corrupt commits would already have been + * dispatched with a `die()`. + */ + if (ignore_missing_commits) + return 0; + return error(_("could not parse commit %s"), + oid_to_hex(&p->object.oid)); + } p->object.flags |= flags; prio_queue_put(&queue, p); } } clear_prio_queue(&queue); - return result; + return 0; } -static struct commit_list *merge_bases_many(struct repository *r, - struct commit *one, int n, - struct commit **twos) +static int merge_bases_many(struct repository *r, + struct commit *one, int n, + struct commit **twos, + struct commit_list **result) { struct commit_list *list = NULL; - struct commit_list *result = NULL; int i; for (i = 0; i < n; i++) { - if (one == twos[i]) + if (one == twos[i]) { /* * We do not mark this even with RESULT so we do not * have to clean it up. */ - return commit_list_insert(one, &result); + *result = commit_list_insert(one, result); + return 0; + } } + if (!one) + return 0; if (repo_parse_commit(r, one)) - return NULL; + return error(_("could not parse commit %s"), + oid_to_hex(&one->object.oid)); for (i = 0; i < n; i++) { + if (!twos[i]) + return 0; if (repo_parse_commit(r, twos[i])) - return NULL; + return error(_("could not parse commit %s"), + oid_to_hex(&twos[i]->object.oid)); } - list = paint_down_to_common(r, one, n, twos, 0); + if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) { + free_commit_list(list); + return -1; + } while (list) { struct commit *commit = pop_commit(&list); if (!(commit->object.flags & STALE)) - commit_list_insert_by_date(commit, &result); + commit_list_insert_by_date(commit, result); } - return result; + return 0; } -struct commit_list *get_octopus_merge_bases(struct commit_list *in) +int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result) { - struct commit_list *i, *j, *k, *ret = NULL; + struct commit_list *i, *j, *k; if (!in) - return ret; + return 0; - commit_list_insert(in->item, &ret); + commit_list_insert(in->item, result); for (i = in->next; i; i = i->next) { struct commit_list *new_commits = NULL, *end = NULL; - for (j = ret; j; j = j->next) { - struct commit_list *bases; - bases = repo_get_merge_bases(the_repository, i->item, - j->item); + for (j = *result; j; j = j->next) { + struct commit_list *bases = NULL; + if (repo_get_merge_bases(the_repository, i->item, + j->item, &bases) < 0) { + free_commit_list(bases); + free_commit_list(*result); + *result = NULL; + return -1; + } if (!new_commits) new_commits = bases; else @@ -172,19 +204,20 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in) for (k = bases; k; k = k->next) end = k; } - free_commit_list(ret); - ret = new_commits; + free_commit_list(*result); + *result = new_commits; } - return ret; + return 0; } static int remove_redundant_no_gen(struct repository *r, - struct commit **array, int cnt) + struct commit **array, + size_t cnt, size_t *dedup_cnt) { struct commit **work; unsigned char *redundant; - int *filled_index; - int i, j, filled; + size_t *filled_index; + size_t i, j, filled; CALLOC_ARRAY(work, cnt); redundant = xcalloc(cnt, 1); @@ -193,7 +226,7 @@ static int remove_redundant_no_gen(struct repository *r, for (i = 0; i < cnt; i++) repo_parse_commit(r, array[i]); for (i = 0; i < cnt; i++) { - struct commit_list *common; + struct commit_list *common = NULL; timestamp_t min_generation = commit_graph_generation(array[i]); if (redundant[i]) @@ -209,8 +242,16 @@ static int remove_redundant_no_gen(struct repository *r, if (curr_generation < min_generation) min_generation = curr_generation; } - common = paint_down_to_common(r, array[i], filled, - work, min_generation); + if (paint_down_to_common(r, array[i], filled, + work, min_generation, 0, &common)) { + clear_commit_marks(array[i], all_flags); + clear_commit_marks_many(filled, work, all_flags); + free_commit_list(common); + free(work); + free(redundant); + free(filled_index); + return -1; + } if (array[i]->object.flags & PARENT2) redundant[i] = 1; for (j = 0; j < filled; j++) @@ -226,20 +267,22 @@ static int remove_redundant_no_gen(struct repository *r, for (i = filled = 0; i < cnt; i++) if (!redundant[i]) array[filled++] = work[i]; + *dedup_cnt = filled; free(work); free(redundant); free(filled_index); - return filled; + return 0; } static int remove_redundant_with_gen(struct repository *r, - struct commit **array, int cnt) + struct commit **array, size_t cnt, + size_t *dedup_cnt) { - int i, count_non_stale = 0, count_still_independent = cnt; + size_t i, count_non_stale = 0, count_still_independent = cnt; timestamp_t min_generation = GENERATION_NUMBER_INFINITY; struct commit **walk_start, **sorted; size_t walk_start_nr = 0, walk_start_alloc = cnt; - int min_gen_pos = 0; + size_t min_gen_pos = 0; /* * Sort the input by generation number, ascending. This allows @@ -285,12 +328,12 @@ static int remove_redundant_with_gen(struct repository *r, * terminate early. Otherwise, we will do the same amount of work * as before. */ - for (i = walk_start_nr - 1; i >= 0 && count_still_independent > 1; i--) { + for (i = walk_start_nr; i && count_still_independent > 1; i--) { /* push the STALE bits up to min generation */ struct commit_list *stack = NULL; - commit_list_insert(walk_start[i], &stack); - walk_start[i]->object.flags |= STALE; + commit_list_insert(walk_start[i - 1], &stack); + walk_start[i - 1]->object.flags |= STALE; while (stack) { struct commit_list *parents; @@ -347,10 +390,12 @@ static int remove_redundant_with_gen(struct repository *r, clear_commit_marks_many(walk_start_nr, walk_start, STALE); free(walk_start); - return count_non_stale; + *dedup_cnt = count_non_stale; + return 0; } -static int remove_redundant(struct repository *r, struct commit **array, int cnt) +static int remove_redundant(struct repository *r, struct commit **array, + size_t cnt, size_t *dedup_cnt) { /* * Some commit in the array may be an ancestor of @@ -360,84 +405,91 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt * that number. */ if (generation_numbers_enabled(r)) { - int i; - /* * If we have a single commit with finite generation * number, then the _with_gen algorithm is preferred. */ - for (i = 0; i < cnt; i++) { + for (size_t i = 0; i < cnt; i++) { if (commit_graph_generation(array[i]) < GENERATION_NUMBER_INFINITY) - return remove_redundant_with_gen(r, array, cnt); + return remove_redundant_with_gen(r, array, cnt, dedup_cnt); } } - return remove_redundant_no_gen(r, array, cnt); + return remove_redundant_no_gen(r, array, cnt, dedup_cnt); } -static struct commit_list *get_merge_bases_many_0(struct repository *r, - struct commit *one, - int n, - struct commit **twos, - int cleanup) +static int get_merge_bases_many_0(struct repository *r, + struct commit *one, + size_t n, + struct commit **twos, + int cleanup, + struct commit_list **result) { struct commit_list *list; struct commit **rslt; - struct commit_list *result; - int cnt, i; + size_t cnt, i; + int ret; - result = merge_bases_many(r, one, n, twos); + if (merge_bases_many(r, one, n, twos, result) < 0) + return -1; for (i = 0; i < n; i++) { if (one == twos[i]) - return result; + return 0; } - if (!result || !result->next) { + if (!*result || !(*result)->next) { if (cleanup) { clear_commit_marks(one, all_flags); clear_commit_marks_many(n, twos, all_flags); } - return result; + return 0; } /* There are more than one */ - cnt = commit_list_count(result); + cnt = commit_list_count(*result); CALLOC_ARRAY(rslt, cnt); - for (list = result, i = 0; list; list = list->next) + for (list = *result, i = 0; list; list = list->next) rslt[i++] = list->item; - free_commit_list(result); + free_commit_list(*result); + *result = NULL; clear_commit_marks(one, all_flags); clear_commit_marks_many(n, twos, all_flags); - cnt = remove_redundant(r, rslt, cnt); - result = NULL; + ret = remove_redundant(r, rslt, cnt, &cnt); + if (ret < 0) { + free(rslt); + return -1; + } for (i = 0; i < cnt; i++) - commit_list_insert_by_date(rslt[i], &result); + commit_list_insert_by_date(rslt[i], result); free(rslt); - return result; + return 0; } -struct commit_list *repo_get_merge_bases_many(struct repository *r, - struct commit *one, - int n, - struct commit **twos) +int repo_get_merge_bases_many(struct repository *r, + struct commit *one, + size_t n, + struct commit **twos, + struct commit_list **result) { - return get_merge_bases_many_0(r, one, n, twos, 1); + return get_merge_bases_many_0(r, one, n, twos, 1, result); } -struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, - struct commit *one, - int n, - struct commit **twos) +int repo_get_merge_bases_many_dirty(struct repository *r, + struct commit *one, + size_t n, + struct commit **twos, + struct commit_list **result) { - return get_merge_bases_many_0(r, one, n, twos, 0); + return get_merge_bases_many_0(r, one, n, twos, 0, result); } -struct commit_list *repo_get_merge_bases(struct repository *r, - struct commit *one, - struct commit *two) +int repo_get_merge_bases(struct repository *r, + struct commit *one, + struct commit *two, + struct commit_list **result) { - return get_merge_bases_many_0(r, one, 1, &two, 1); + return get_merge_bases_many_0(r, one, 1, &two, 1, result); } /* @@ -460,11 +512,13 @@ int repo_is_descendant_of(struct repository *r, } else { while (with_commit) { struct commit *other; + int ret; other = with_commit->item; with_commit = with_commit->next; - if (repo_in_merge_bases_many(r, other, 1, &commit)) - return 1; + ret = repo_in_merge_bases_many(r, other, 1, &commit, 0); + if (ret) + return ret; } return 0; } @@ -474,17 +528,18 @@ int repo_is_descendant_of(struct repository *r, * Is "commit" an ancestor of one of the "references"? */ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, - int nr_reference, struct commit **reference) + int nr_reference, struct commit **reference, + int ignore_missing_commits) { - struct commit_list *bases; + struct commit_list *bases = NULL; int ret = 0, i; timestamp_t generation, max_generation = GENERATION_NUMBER_ZERO; if (repo_parse_commit(r, commit)) - return ret; + return ignore_missing_commits ? 0 : -1; for (i = 0; i < nr_reference; i++) { if (repo_parse_commit(r, reference[i])) - return ret; + return ignore_missing_commits ? 0 : -1; generation = commit_graph_generation(reference[i]); if (generation > max_generation) @@ -495,10 +550,11 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, if (generation > max_generation) return ret; - bases = paint_down_to_common(r, commit, - nr_reference, reference, - generation); - if (commit->object.flags & PARENT2) + if (paint_down_to_common(r, commit, + nr_reference, reference, + generation, ignore_missing_commits, &bases)) + ret = -1; + else if (commit->object.flags & PARENT2) ret = 1; clear_commit_marks(commit, all_flags); clear_commit_marks_many(nr_reference, reference, all_flags); @@ -529,7 +585,8 @@ struct commit_list *reduce_heads(struct commit_list *heads) struct commit_list *p; struct commit_list *result = NULL, **tail = &result; struct commit **array; - int num_head, i; + size_t num_head, i; + int ret; if (!heads) return NULL; @@ -550,7 +607,13 @@ struct commit_list *reduce_heads(struct commit_list *heads) p->item->object.flags &= ~STALE; } } - num_head = remove_redundant(the_repository, array, num_head); + + ret = remove_redundant(the_repository, array, num_head, &num_head); + if (ret < 0) { + free(array); + return NULL; + } + for (i = 0; i < num_head; i++) tail = &commit_list_insert(array[i], tail)->next; free(array); @@ -593,6 +656,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid) commit_list_insert(old_commit, &old_commit_list); ret = repo_is_descendant_of(the_repository, new_commit, old_commit_list); + if (ret < 0) + exit(128); free_commit_list(old_commit_list); return ret; } @@ -721,12 +786,12 @@ int commit_contains(struct ref_filter *filter, struct commit *commit, int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, - time_t min_commit_date, + timestamp_t min_commit_date, timestamp_t min_generation) { struct commit **list = NULL; - int i; - int nr_commits; + size_t i; + size_t nr_commits; int result = 1; ALLOC_ARRAY(list, from->nr); @@ -824,9 +889,9 @@ 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; - time_t min_commit_date = cutoff_by_min_date ? from->item->date : 0; 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) { @@ -878,8 +943,8 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to, return result; } -struct commit_list *get_reachable_subset(struct commit **from, int nr_from, - struct commit **to, int nr_to, +struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from, + struct commit **to, size_t nr_to, unsigned int reachable_flag) { struct commit **item; @@ -1049,6 +1114,10 @@ void ahead_behind(struct repository *r, /* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks(r, PARENT2 | STALE); + while (prio_queue_peek(&queue)) { + struct commit *c = prio_queue_get(&queue); + free_bit_array(c); + } clear_bit_arrays(&bit_arrays); clear_prio_queue(&queue); } @@ -1164,4 +1233,131 @@ void tips_reachable_from_bases(struct repository *r, done: free(commits); repo_clear_commit_marks(r, SEEN); + free_commit_list(stack); +} + +/* + * This slab initializes integers to zero, so use "-1" for "tip is best" and + * "i + 1" for "bases[i] is best". + */ +define_commit_slab(best_branch_base, int); +static struct best_branch_base best_branch_base; +#define get_best(c) (*best_branch_base_at(&best_branch_base, (c))) +#define set_best(c,v) (*best_branch_base_at(&best_branch_base, (c)) = (v)) + +int get_branch_base_for_tip(struct repository *r, + struct commit *tip, + struct commit **bases, + size_t bases_nr) +{ + int best_index = -1; + struct commit *branch_point = NULL; + struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; + int found_missing_gen = 0; + + if (!bases_nr) + return -1; + + repo_parse_commit(r, tip); + if (commit_graph_generation(tip) == GENERATION_NUMBER_INFINITY) + found_missing_gen = 1; + + /* Check for missing generation numbers. */ + for (size_t i = 0; i < bases_nr; i++) { + struct commit *c = bases[i]; + repo_parse_commit(r, c); + if (commit_graph_generation(c) == GENERATION_NUMBER_INFINITY) + found_missing_gen = 1; + } + + if (found_missing_gen) { + struct commit **commits; + size_t commits_nr = bases_nr + 1; + + CALLOC_ARRAY(commits, commits_nr); + COPY_ARRAY(commits, bases, bases_nr); + commits[bases_nr] = tip; + ensure_generations_valid(r, commits, commits_nr); + free(commits); + } + + /* Initialize queue and slab now that generations are guaranteed. */ + init_best_branch_base(&best_branch_base); + set_best(tip, -1); + prio_queue_put(&queue, tip); + + for (size_t i = 0; i < bases_nr; i++) { + struct commit *c = bases[i]; + int best = get_best(c); + + /* Has this already been marked as best by another commit? */ + if (best) { + if (best == -1) { + /* We agree at this position. Stop now. */ + best_index = i + 1; + goto cleanup; + } + continue; + } + + set_best(c, i + 1); + prio_queue_put(&queue, c); + } + + while (queue.nr) { + struct commit *c = prio_queue_get(&queue); + int best_for_c = get_best(c); + int best_for_p, positive; + struct commit *parent; + + /* Have we reached a known branch point? It's optimal. */ + if (c == branch_point) + break; + + repo_parse_commit(r, c); + if (!c->parents) + continue; + + parent = c->parents->item; + repo_parse_commit(r, parent); + best_for_p = get_best(parent); + + if (!best_for_p) { + /* 'parent' is new, so pass along best_for_c. */ + set_best(parent, best_for_c); + prio_queue_put(&queue, parent); + continue; + } + + if (best_for_p > 0 && best_for_c > 0) { + /* Collision among bases. Minimize. */ + if (best_for_c < best_for_p) + set_best(parent, best_for_c); + continue; + } + + /* + * At this point, we have reached a commit that is reachable + * from the tip, either from 'c' or from an earlier commit to + * have 'parent' as its first parent. + * + * Update 'best_index' to match the minimum of all base indices + * to reach 'parent'. + */ + + /* Exactly one is positive due to initial conditions. */ + positive = (best_for_c < 0) ? best_for_p : best_for_c; + + if (best_index < 0 || positive < best_index) + best_index = positive; + + /* No matter what, track that the parent is reachable from tip. */ + set_best(parent, -1); + branch_point = parent; + } + +cleanup: + clear_best_branch_base(&best_branch_base); + clear_prio_queue(&queue); + return best_index > 0 ? best_index - 1 : -1; } |
