From e67431d4965d13b185db3ddb4445c3c7034ca62d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:07 +0000 Subject: [PATCH 01/11] commit-reach(paint_down_to_common): plug two memory leaks When a commit is missing, we return early (currently pretending that no merge basis could be found in that case). At that stage, it is possible that a merge base could have been found already, and added to the `result`, which is now leaked. The priority queue has a similar issue: There might still be a commit in that queue. Let's release both, to address the potential memory leaks. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/commit-reach.c b/commit-reach.c index ecc913fc99..bfa1b6454d 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -104,8 +104,11 @@ 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)) + if (repo_parse_commit(r, p)) { + clear_prio_queue(&queue); + free_commit_list(result); return NULL; + } p->object.flags |= flags; prio_queue_put(&queue, p); } From 207c40e1e43c992a8b268a3395ca104566612c6e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:08 +0000 Subject: [PATCH 02/11] commit-reach(repo_in_merge_bases_many): optionally expect missing commits Currently this function treats unrelated commit histories the same way as commit histories with missing commit objects. Typically, missing commit objects constitute a corrupt repository, though, and should be reported as such. The next commits will make it so, but there is one exception: In `git fetch --update-shallow` we _expect_ commit objects to be missing, and we do want to treat the now-incomplete commit histories as unrelated. To allow for that, let's introduce an additional parameter that is passed to `repo_in_merge_bases_many()` to trigger this behavior, and use it in the two callers in `shallow.c`. This commit changes behavior slightly: unless called from the `shallow.c` functions that set the `ignore_missing_commits` bit, any non-existing tip commit that is passed to `repo_in_merge_bases_many()` will now result in an error. Note: When encountering missing commits while traversing the commit history in search for merge bases, with this commit there won't be a change in behavior just yet, their children will still be interpreted as root commits. This bug will get fixed by follow-up commits. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 9 +++++---- commit-reach.h | 3 ++- remote.c | 2 +- shallow.c | 5 +++-- t/helper/test-reach.c | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index bfa1b6454d..e81bee421a 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -466,7 +466,7 @@ int repo_is_descendant_of(struct repository *r, other = with_commit->item; with_commit = with_commit->next; - if (repo_in_merge_bases_many(r, other, 1, &commit)) + if (repo_in_merge_bases_many(r, other, 1, &commit, 0)) return 1; } return 0; @@ -477,17 +477,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; 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) diff --git a/commit-reach.h b/commit-reach.h index 35c4da4948..68f81549a4 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -30,7 +30,8 @@ int repo_in_merge_bases(struct repository *r, struct commit *reference); 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); /* * Takes a list of commits and returns a new list where those diff --git a/remote.c b/remote.c index e07b316eac..b3dbdd9b59 100644 --- a/remote.c +++ b/remote.c @@ -2680,7 +2680,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote) if (MERGE_BASES_BATCH_SIZE < size) size = MERGE_BASES_BATCH_SIZE; - if ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk))) + if ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk, 0))) break; } diff --git a/shallow.c b/shallow.c index 7711798127..998072d04a 100644 --- a/shallow.c +++ b/shallow.c @@ -796,7 +796,7 @@ static void post_assign_shallow(struct shallow_info *info, for (j = 0; j < bitmap_nr; j++) if (bitmap[0][j] && /* Step 7, reachability test at commit level */ - !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) { + !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) { update_refstatus(ref_status, info->ref->nr, *bitmap); dst++; break; @@ -827,7 +827,8 @@ int delayed_reachability_test(struct shallow_info *si, int c) si->reachable[c] = repo_in_merge_bases_many(the_repository, commit, si->nr_commits, - si->commits); + si->commits, + 1); si->need_reachability_test[c] = 0; } return si->reachable[c]; diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index 1e159a754d..c96b62ff1e 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c @@ -111,7 +111,7 @@ int cmd__reach(int ac, const char **av) repo_in_merge_bases(the_repository, A, B)); else if (!strcmp(av[1], "in_merge_bases_many")) printf("%s(A,X):%d\n", av[1], - repo_in_merge_bases_many(the_repository, A, X_nr, X_array)); + repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0)); else if (!strcmp(av[1], "is_descendant_of")) printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X)); else if (!strcmp(av[1], "get_merge_bases_many")) { From 24876ebf68baf90075dad5ca3acba8a305f308d4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:09 +0000 Subject: [PATCH 03/11] commit-reach(repo_in_merge_bases_many): report missing commits Some functions in Git's source code follow the convention that returning a negative value indicates a fatal error, e.g. repository corruption. Let's use this convention in `repo_in_merge_bases()` to report when one of the specified commits is missing (i.e. when `repo_parse_commit()` reports an error). Also adjust the callers of `repo_in_merge_bases()` to handle such negative return values. Note: As of this patch, errors are returned only if any of the specified merge heads is missing. Over the course of the next patches, missing commits will also be reported by the `paint_down_to_common()` function, which is called by `repo_in_merge_bases_many()`, and those errors will be properly propagated back to the caller at that stage. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/branch.c | 12 +++++-- builtin/fast-import.c | 6 +++- builtin/fetch.c | 2 ++ builtin/log.c | 7 ++-- builtin/merge-base.c | 6 +++- builtin/pull.c | 4 +++ builtin/receive-pack.c | 6 +++- commit-reach.c | 8 +++-- http-push.c | 5 ++- merge-ort.c | 81 ++++++++++++++++++++++++++++++++++++------ merge-recursive.c | 54 +++++++++++++++++++++++----- shallow.c | 18 ++++++---- 12 files changed, 173 insertions(+), 36 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index cfb63cce5f..b3cbb7fd44 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -158,6 +158,8 @@ static int branch_merged(int kind, const char *name, merged = reference_rev ? repo_in_merge_bases(the_repository, rev, reference_rev) : 0; + if (merged < 0) + exit(128); /* * After the safety valve is fully redefined to "check with @@ -166,9 +168,13 @@ static int branch_merged(int kind, const char *name, * any of the following code, but during the transition period, * a gentle reminder is in order. */ - if ((head_rev != reference_rev) && - (head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != merged) { - if (merged) + if (head_rev != reference_rev) { + int expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0; + if (expect < 0) + exit(128); + if (expect == merged) + ; /* okay */ + else if (merged) warning(_("deleting branch '%s' that has been merged to\n" " '%s', but not yet merged to HEAD"), name, reference_name); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 92eda20683..71a195ca22 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -1625,6 +1625,7 @@ static int update_branch(struct branch *b) oidclr(&old_oid); if (!force_update && !is_null_oid(&old_oid)) { struct commit *old_cmit, *new_cmit; + int ret; old_cmit = lookup_commit_reference_gently(the_repository, &old_oid, 0); @@ -1633,7 +1634,10 @@ static int update_branch(struct branch *b) if (!old_cmit || !new_cmit) return error("Branch %s is missing commits.", b->name); - if (!repo_in_merge_bases(the_repository, old_cmit, new_cmit)) { + ret = repo_in_merge_bases(the_repository, old_cmit, new_cmit); + if (ret < 0) + exit(128); + if (!ret) { warning("Not updating %s" " (new tip %s does not contain %s)", b->name, oid_to_hex(&b->oid), diff --git a/builtin/fetch.c b/builtin/fetch.c index 3aedfd1bb6..3b2edddb1c 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -982,6 +982,8 @@ static int update_local_ref(struct ref *ref, uint64_t t_before = getnanotime(); fast_forward = repo_in_merge_bases(the_repository, current, updated); + if (fast_forward < 0) + exit(128); forced_updates_ms += (getnanotime() - t_before) / 1000000; } else { fast_forward = 1; diff --git a/builtin/log.c b/builtin/log.c index db1808d7c1..0631922886 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1625,7 +1625,7 @@ static struct commit *get_base_commit(const char *base_commit, { struct commit *base = NULL; struct commit **rev; - int i = 0, rev_nr = 0, auto_select, die_on_failure; + int i = 0, rev_nr = 0, auto_select, die_on_failure, ret; switch (auto_base) { case AUTO_BASE_NEVER: @@ -1725,7 +1725,10 @@ static struct commit *get_base_commit(const char *base_commit, rev_nr = DIV_ROUND_UP(rev_nr, 2); } - if (!repo_in_merge_bases(the_repository, base, rev[0])) { + ret = repo_in_merge_bases(the_repository, base, rev[0]); + if (ret < 0) + exit(128); + if (!ret) { if (die_on_failure) { die(_("base commit should be the ancestor of revision list")); } else { diff --git a/builtin/merge-base.c b/builtin/merge-base.c index d26e8fbf6f..b0b3838d5f 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -100,12 +100,16 @@ static int handle_octopus(int count, const char **args, int show_all) static int handle_is_ancestor(int argc, const char **argv) { struct commit *one, *two; + int ret; if (argc != 2) die("--is-ancestor takes exactly two commits"); one = get_commit_reference(argv[0]); two = get_commit_reference(argv[1]); - if (repo_in_merge_bases(the_repository, one, two)) + ret = repo_in_merge_bases(the_repository, one, two); + if (ret < 0) + exit(128); + if (ret) return 0; else return 1; diff --git a/builtin/pull.c b/builtin/pull.c index 73a68b75b0..3daff0e8b9 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -926,6 +926,8 @@ static int get_can_ff(struct object_id *orig_head, merge_head = lookup_commit_reference(the_repository, orig_merge_head); ret = repo_is_descendant_of(the_repository, merge_head, list); free_commit_list(list); + if (ret < 0) + exit(128); return ret; } @@ -950,6 +952,8 @@ static int already_up_to_date(struct object_id *orig_head, commit_list_insert(theirs, &list); ok = repo_is_descendant_of(the_repository, ours, list); free_commit_list(list); + if (ok < 0) + exit(128); if (!ok) return 0; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index db65607485..56d8a77ed7 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1526,6 +1526,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) starts_with(name, "refs/heads/")) { struct object *old_object, *new_object; struct commit *old_commit, *new_commit; + int ret2; old_object = parse_object(the_repository, old_oid); new_object = parse_object(the_repository, new_oid); @@ -1539,7 +1540,10 @@ static const char *update(struct command *cmd, struct shallow_info *si) } old_commit = (struct commit *)old_object; new_commit = (struct commit *)new_object; - if (!repo_in_merge_bases(the_repository, old_commit, new_commit)) { + ret2 = repo_in_merge_bases(the_repository, old_commit, new_commit); + if (ret2 < 0) + exit(128); + if (!ret2) { rp_error("denying non-fast-forward %s" " (you should pull first)", name); ret = "non-fast-forward"; diff --git a/commit-reach.c b/commit-reach.c index e81bee421a..1c51213978 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -463,11 +463,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, 0)) - return 1; + ret = repo_in_merge_bases_many(r, other, 1, &commit, 0); + if (ret) + return ret; } return 0; } @@ -597,6 +599,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; } diff --git a/http-push.c b/http-push.c index 12d1113741..33db41bfac 100644 --- a/http-push.c +++ b/http-push.c @@ -1575,8 +1575,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote) struct commit *head = lookup_commit_or_die(head_oid, "HEAD"); struct commit *branch = lookup_commit_or_die(&remote->old_oid, remote->name); + int ret = repo_in_merge_bases(the_repository, branch, head); - return repo_in_merge_bases(the_repository, branch, head); + if (ret < 0) + exit(128); + return ret; } static int delete_remote_branch(const char *pattern, int force) diff --git a/merge-ort.c b/merge-ort.c index 8617babee4..79927954f5 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -542,6 +542,7 @@ enum conflict_and_info_types { CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, CONFLICT_SUBMODULE_NULL_MERGE_BASE, + CONFLICT_SUBMODULE_CORRUPT, /* Keep this entry _last_ in the list */ NB_CONFLICT_TYPES, @@ -594,7 +595,9 @@ static const char *type_short_descriptions[] = { [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = "CONFLICT (submodule may have rewinds)", [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = - "CONFLICT (submodule lacks merge base)" + "CONFLICT (submodule lacks merge base)", + [CONFLICT_SUBMODULE_CORRUPT] = + "CONFLICT (submodule corrupt)" }; struct logical_conflict_info { @@ -1708,7 +1711,14 @@ static int find_first_merges(struct repository *repo, die("revision walk setup failed"); while ((commit = get_revision(&revs)) != NULL) { struct object *o = &(commit->object); - if (repo_in_merge_bases(repo, b, commit)) + int ret = repo_in_merge_bases(repo, b, commit); + + if (ret < 0) { + object_array_clear(&merges); + release_revisions(&revs); + return ret; + } + if (ret > 0) add_object_array(o, NULL, &merges); } reset_revision_walk(); @@ -1723,9 +1733,17 @@ static int find_first_merges(struct repository *repo, contains_another = 0; for (j = 0; j < merges.nr; j++) { struct commit *m2 = (struct commit *) merges.objects[j].item; - if (i != j && repo_in_merge_bases(repo, m2, m1)) { - contains_another = 1; - break; + if (i != j) { + int ret = repo_in_merge_bases(repo, m2, m1); + if (ret < 0) { + object_array_clear(&merges); + release_revisions(&revs); + return ret; + } + if (ret > 0) { + contains_another = 1; + break; + } } } @@ -1747,7 +1765,7 @@ static int merge_submodule(struct merge_options *opt, { struct repository subrepo; struct strbuf sb = STRBUF_INIT; - int ret = 0; + int ret = 0, ret2; struct commit *commit_o, *commit_a, *commit_b; int parent_count; struct object_array merges; @@ -1794,8 +1812,26 @@ static int merge_submodule(struct merge_options *opt, } /* check whether both changes are forward */ - if (!repo_in_merge_bases(&subrepo, commit_o, commit_a) || - !repo_in_merge_bases(&subrepo, commit_o, commit_b)) { + ret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a); + if (ret2 < 0) { + path_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s " + "(repository corrupt)"), + path); + goto cleanup; + } + if (ret2 > 0) + ret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b); + if (ret2 < 0) { + path_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s " + "(repository corrupt)"), + path); + goto cleanup; + } + if (!ret2) { path_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s " @@ -1805,7 +1841,16 @@ static int merge_submodule(struct merge_options *opt, } /* Case #1: a is contained in b or vice versa */ - if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) { + ret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b); + if (ret2 < 0) { + path_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s " + "(repository corrupt)"), + path); + goto cleanup; + } + if (ret2 > 0) { oidcpy(result, b); path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, path, NULL, NULL, NULL, @@ -1814,7 +1859,16 @@ static int merge_submodule(struct merge_options *opt, ret = 1; goto cleanup; } - if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) { + ret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a); + if (ret2 < 0) { + path_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s " + "(repository corrupt)"), + path); + goto cleanup; + } + if (ret2 > 0) { oidcpy(result, a); path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, path, NULL, NULL, NULL, @@ -1839,6 +1893,13 @@ static int merge_submodule(struct merge_options *opt, parent_count = find_first_merges(&subrepo, path, commit_a, commit_b, &merges); switch (parent_count) { + case -1: + path_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s " + "(repository corrupt)"), + path); + break; case 0: path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0, path, NULL, NULL, NULL, diff --git a/merge-recursive.c b/merge-recursive.c index a0c3e7a2d9..ae23a62048 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1139,7 +1139,13 @@ static int find_first_merges(struct repository *repo, die("revision walk setup failed"); while ((commit = get_revision(&revs)) != NULL) { struct object *o = &(commit->object); - if (repo_in_merge_bases(repo, b, commit)) + int ret = repo_in_merge_bases(repo, b, commit); + if (ret < 0) { + object_array_clear(&merges); + release_revisions(&revs); + return ret; + } + if (ret) add_object_array(o, NULL, &merges); } reset_revision_walk(); @@ -1154,9 +1160,17 @@ static int find_first_merges(struct repository *repo, contains_another = 0; for (j = 0; j < merges.nr; j++) { struct commit *m2 = (struct commit *) merges.objects[j].item; - if (i != j && repo_in_merge_bases(repo, m2, m1)) { - contains_another = 1; - break; + if (i != j) { + int ret = repo_in_merge_bases(repo, m2, m1); + if (ret < 0) { + object_array_clear(&merges); + release_revisions(&revs); + return ret; + } + if (ret > 0) { + contains_another = 1; + break; + } } } @@ -1192,7 +1206,7 @@ static int merge_submodule(struct merge_options *opt, const struct object_id *b) { struct repository subrepo; - int ret = 0; + int ret = 0, ret2; struct commit *commit_base, *commit_a, *commit_b; int parent_count; struct object_array merges; @@ -1229,14 +1243,29 @@ static int merge_submodule(struct merge_options *opt, } /* check whether both changes are forward */ - if (!repo_in_merge_bases(&subrepo, commit_base, commit_a) || - !repo_in_merge_bases(&subrepo, commit_base, commit_b)) { + ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a); + if (ret2 < 0) { + output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + goto cleanup; + } + if (ret2 > 0) + ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b); + if (ret2 < 0) { + output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + goto cleanup; + } + if (!ret2) { output(opt, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path); goto cleanup; } /* Case #1: a is contained in b or vice versa */ - if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) { + ret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b); + if (ret2 < 0) { + output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + goto cleanup; + } + if (ret2) { oidcpy(result, b); if (show(opt, 3)) { output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path); @@ -1249,7 +1278,12 @@ static int merge_submodule(struct merge_options *opt, ret = 1; goto cleanup; } - if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) { + ret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a); + if (ret2 < 0) { + output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + goto cleanup; + } + if (ret2) { oidcpy(result, a); if (show(opt, 3)) { output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path); @@ -1397,6 +1431,8 @@ static int merge_mode_and_contents(struct merge_options *opt, &o->oid, &a->oid, &b->oid); + if (result->clean < 0) + return -1; } else if (S_ISLNK(a->mode)) { switch (opt->recursive_variant) { case MERGE_VARIANT_NORMAL: diff --git a/shallow.c b/shallow.c index 998072d04a..7ff50dd0da 100644 --- a/shallow.c +++ b/shallow.c @@ -794,12 +794,16 @@ static void post_assign_shallow(struct shallow_info *info, if (!*bitmap) continue; for (j = 0; j < bitmap_nr; j++) - if (bitmap[0][j] && - /* Step 7, reachability test at commit level */ - !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) { - update_refstatus(ref_status, info->ref->nr, *bitmap); - dst++; - break; + if (bitmap[0][j]) { + /* Step 7, reachability test at commit level */ + int ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1); + if (ret < 0) + exit(128); + if (!ret) { + update_refstatus(ref_status, info->ref->nr, *bitmap); + dst++; + break; + } } } info->nr_ours = dst; @@ -829,6 +833,8 @@ int delayed_reachability_test(struct shallow_info *si, int c) si->nr_commits, si->commits, 1); + if (si->reachable[c] < 0) + exit(128); si->need_reachability_test[c] = 0; } return si->reachable[c]; From 2d2da172f37f5a406a0f5a099cd268bcb1bd7d42 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:10 +0000 Subject: [PATCH 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits When `git fetch --update-shallow` needs to test for commit ancestry, it can naturally run into a missing object (e.g. if it is a parent of a shallow commit). For the purpose of `--update-shallow`, this needs to be treated as if the child commit did not even have that parent, i.e. the commit history needs to be clamped. For all other scenarios, clamping the commit history is actually a bug, as it would hide repository corruption (for an analysis regarding shallow and partial clones, see the analysis further down). Add a flag to optionally ask the function to ignore missing commits, as `--update-shallow` needs it to, while detecting missing objects as a repository corruption error by default. This flag is needed, and cannot be replaced by `is_repository_shallow()` to indicate that situation, because that function would return 0 in the `--update-shallow` scenario: There is not actually a `shallow` file in that scenario, as demonstrated e.g. by t5537.10 ("add new shallow root with receive.updateshallow on") and t5538.4 ("add new shallow root with receive.updateshallow on"). Note: shallow commits' parents are set to `NULL` internally already, therefore there is no need to special-case shallow repositories here, as the merge-base logic will not try to access parent commits of shallow commits. Likewise, partial clones aren't an issue either: If a commit is missing during the revision walk in the merge-base logic, it is fetched via `promisor_remote_get_direct()`. And not only the single missing commit object: Due to the way the "promised" objects are fetched (in `fetch_objects()` in `promisor-remote.c`, using `fetch --filter=blob:none`), there is no actual way to fetch a single commit object, as the remote side will pass that commit OID to `pack-objects --revs [...]` which in turn passes it to `rev-list` which interprets this as a commit _range_ instead of a single object. Therefore, in partial clones (unless they are shallow in addition), all commits reachable from a commit that is in the local object database are also present in that local database. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index 1c51213978..616738fc4e 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -52,7 +52,8 @@ static int queue_has_nonstale(struct prio_queue *queue) static struct commit_list *paint_down_to_common(struct repository *r, struct commit *one, int n, struct commit **twos, - timestamp_t min_generation) + timestamp_t min_generation, + int ignore_missing_commits) { struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct commit_list *result = NULL; @@ -107,6 +108,13 @@ static struct commit_list *paint_down_to_common(struct repository *r, if (repo_parse_commit(r, p)) { clear_prio_queue(&queue); free_commit_list(result); + /* + * 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()`. + */ return NULL; } p->object.flags |= flags; @@ -142,7 +150,7 @@ static struct commit_list *merge_bases_many(struct repository *r, return NULL; } - list = paint_down_to_common(r, one, n, twos, 0); + list = paint_down_to_common(r, one, n, twos, 0, 0); while (list) { struct commit *commit = pop_commit(&list); @@ -213,7 +221,7 @@ static int remove_redundant_no_gen(struct repository *r, min_generation = curr_generation; } common = paint_down_to_common(r, array[i], filled, - work, min_generation); + work, min_generation, 0); if (array[i]->object.flags & PARENT2) redundant[i] = 1; for (j = 0; j < filled; j++) @@ -503,7 +511,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, bases = paint_down_to_common(r, commit, nr_reference, reference, - generation); + generation, ignore_missing_commits); if (commit->object.flags & PARENT2) ret = 1; clear_commit_marks(commit, all_flags); From 896a0e11f37687fb3d40c0aa2b28062264232823 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:11 +0000 Subject: [PATCH 05/11] commit-reach(paint_down_to_common): start reporting errors If a commit cannot be parsed, it is currently ignored when looking for merge bases. That's undesirable as the operation can pretend success in a corrupt repository, even though the command should fail with an error message. Let's start at the bottom of the stack by teaching the `paint_down_to_common()` function to return an `int`: if negative, it indicates fatal error, if 0 success. This requires a couple of callers to be adjusted accordingly. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 66 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index 616738fc4e..267fcfc6ca 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -49,14 +49,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, - int ignore_missing_commits) +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; @@ -65,8 +65,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); @@ -94,7 +94,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; @@ -107,7 +107,8 @@ static struct commit_list *paint_down_to_common(struct repository *r, continue; if (repo_parse_commit(r, p)) { clear_prio_queue(&queue); - free_commit_list(result); + free_commit_list(*result); + *result = NULL; /* * At this stage, we know that the commit is * missing: `repo_parse_commit()` uses @@ -115,7 +116,10 @@ static struct commit_list *paint_down_to_common(struct repository *r, * corrupt commits would already have been * dispatched with a `die()`. */ - return NULL; + 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); @@ -123,7 +127,7 @@ static struct commit_list *paint_down_to_common(struct repository *r, } clear_prio_queue(&queue); - return result; + return 0; } static struct commit_list *merge_bases_many(struct repository *r, @@ -150,7 +154,10 @@ static struct commit_list *merge_bases_many(struct repository *r, return NULL; } - list = paint_down_to_common(r, one, n, twos, 0, 0); + if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) { + free_commit_list(list); + return NULL; + } while (list) { struct commit *commit = pop_commit(&list); @@ -204,7 +211,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]) @@ -220,8 +227,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, 0); + 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++) @@ -421,6 +436,10 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r, clear_commit_marks_many(n, twos, all_flags); cnt = remove_redundant(r, rslt, cnt); + if (cnt < 0) { + free(rslt); + return NULL; + } result = NULL; for (i = 0; i < cnt; i++) commit_list_insert_by_date(rslt[i], &result); @@ -490,7 +509,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, 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; @@ -509,10 +528,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, ignore_missing_commits); - 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); @@ -565,6 +585,10 @@ struct commit_list *reduce_heads(struct commit_list *heads) } } num_head = remove_redundant(the_repository, array, num_head); + if (num_head < 0) { + free(array); + return NULL; + } for (i = 0; i < num_head; i++) tail = &commit_list_insert(array[i], tail)->next; free(array); From fb02c523a317937a4080315c2d8f8151730b87be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:12 +0000 Subject: [PATCH 06/11] commit-reach(merge_bases_many): pass on "missing commits" errors The `paint_down_to_common()` function was just taught to indicate parsing errors, and now the `merge_bases_many()` function is aware of that, too. One tricky aspect is that `merge_bases_many()` parses commits of its own, but wants to gracefully handle the scenario where NULL is passed as a merge head, returning the empty list of merge bases. The way this was handled involved calling `repo_parse_commit(NULL)` and relying on it to return an error. This has to be done differently now so that we can handle missing commits correctly by producing a fatal error. Next step: adjust the caller of `merge_bases_many()`: `get_merge_bases_many_0()`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index 267fcfc6ca..2585b895d3 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -130,41 +130,49 @@ static int paint_down_to_common(struct repository *r, 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)); } if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) { free_commit_list(list); - return NULL; + 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) @@ -409,10 +417,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r, { struct commit_list *list; struct commit **rslt; - struct commit_list *result; + struct commit_list *result = NULL; int cnt, i; - result = merge_bases_many(r, one, n, twos); + if (merge_bases_many(r, one, n, twos, &result) < 0) + return NULL; for (i = 0; i < n; i++) { if (one == twos[i]) return result; From 8226e157a92066855811188f7ca3fd4bff5f083d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:13 +0000 Subject: [PATCH 07/11] commit-reach(get_merge_bases_many_0): pass on "missing commits" errors The `merge_bases_many()` function was just taught to indicate parsing errors, and now the `get_merge_bases_many_0()` function is aware of that, too. Next step: adjust the callers of `get_merge_bases_many_0()`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- commit-reach.c | 57 +++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index 2585b895d3..95cbfae0eb 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -409,37 +409,38 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt return remove_redundant_no_gen(r, array, 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, + int n, + struct commit **twos, + int cleanup, + struct commit_list **result) { struct commit_list *list; struct commit **rslt; - struct commit_list *result = NULL; int cnt, i; - if (merge_bases_many(r, one, n, twos, &result) < 0) - return NULL; + 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); @@ -447,13 +448,12 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r, cnt = remove_redundant(r, rslt, cnt); if (cnt < 0) { free(rslt); - return NULL; + return -1; } - result = NULL; 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, @@ -461,7 +461,12 @@ struct commit_list *repo_get_merge_bases_many(struct repository *r, int n, struct commit **twos) { - return get_merge_bases_many_0(r, one, n, twos, 1); + struct commit_list *result = NULL; + if (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) { + free_commit_list(result); + return NULL; + } + return result; } struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, @@ -469,14 +474,24 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, int n, struct commit **twos) { - return get_merge_bases_many_0(r, one, n, twos, 0); + struct commit_list *result = NULL; + if (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) { + free_commit_list(result); + return NULL; + } + return result; } struct commit_list *repo_get_merge_bases(struct repository *r, struct commit *one, struct commit *two) { - return get_merge_bases_many_0(r, one, 1, &two, 1); + struct commit_list *result = NULL; + if (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) { + free_commit_list(result); + return NULL; + } + return result; } /* From 76e2a0999907644966dfe48b573d6e57e2f1e275 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:14 +0000 Subject: [PATCH 08/11] commit-reach(repo_get_merge_bases): pass on "missing commits" errors The `merge_bases_many()` function was just taught to indicate parsing errors, and now the `repo_get_merge_bases()` function (which is also surfaced via the `repo_get_merge_bases()` macro) is aware of that, too. Naturally, there are a lot of callers that need to be adjusted now, too. Next step: adjust the callers of `get_octopus_merge_bases()`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/log.c | 10 +++++----- builtin/merge-tree.c | 5 +++-- builtin/merge.c | 20 ++++++++++++-------- builtin/rebase.c | 8 +++++--- builtin/rev-parse.c | 5 +++-- commit-reach.c | 23 +++++++++++------------ commit-reach.h | 7 ++++--- diff-lib.c | 5 +++-- log-tree.c | 5 +++-- merge-ort.c | 6 +++++- merge-recursive.c | 4 +++- notes-merge.c | 3 ++- object-name.c | 7 +++++-- revision.c | 12 ++++++++---- sequencer.c | 8 ++++++-- submodule.c | 7 ++++++- t/t4301-merge-tree-write-tree.sh | 12 ++++++++++++ 17 files changed, 96 insertions(+), 51 deletions(-) diff --git a/builtin/log.c b/builtin/log.c index 0631922886..b7147f893b 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1704,11 +1704,11 @@ static struct commit *get_base_commit(const char *base_commit, */ while (rev_nr > 1) { for (i = 0; i < rev_nr / 2; i++) { - struct commit_list *merge_base; - merge_base = repo_get_merge_bases(the_repository, - rev[2 * i], - rev[2 * i + 1]); - if (!merge_base || merge_base->next) { + struct commit_list *merge_base = NULL; + if (repo_get_merge_bases(the_repository, + rev[2 * i], + rev[2 * i + 1], &merge_base) < 0 || + !merge_base || merge_base->next) { if (die_on_failure) { die(_("failed to find exact merge base")); } else { diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 3bdec53fbe..8a460d5529 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -462,8 +462,9 @@ static int real_merge(struct merge_tree_options *o, * Get the merge bases, in reverse order; see comment above * merge_incore_recursive in merge-ort.h */ - merge_bases = repo_get_merge_bases(the_repository, parent1, - parent2); + if (repo_get_merge_bases(the_repository, parent1, + parent2, &merge_bases) < 0) + exit(128); if (!merge_bases && !o->allow_unrelated_histories) die(_("refusing to merge unrelated histories")); merge_bases = reverse_commit_list(merge_bases); diff --git a/builtin/merge.c b/builtin/merge.c index 8f819781cc..7c0189fb8f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1514,10 +1514,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!remoteheads) ; /* already up-to-date */ - else if (!remoteheads->next) - common = repo_get_merge_bases(the_repository, head_commit, - remoteheads->item); - else { + else if (!remoteheads->next) { + if (repo_get_merge_bases(the_repository, head_commit, + remoteheads->item, &common) < 0) { + ret = 2; + goto done; + } + } else { struct commit_list *list = remoteheads; commit_list_insert(head_commit, &list); common = get_octopus_merge_bases(list); @@ -1627,7 +1630,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit_list *j; for (j = remoteheads; j; j = j->next) { - struct commit_list *common_one; + struct commit_list *common_one = NULL; struct commit *common_item; /* @@ -1635,9 +1638,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * merge_bases again, otherwise "git merge HEAD^ * HEAD^^" would be missed. */ - common_one = repo_get_merge_bases(the_repository, - head_commit, - j->item); + if (repo_get_merge_bases(the_repository, head_commit, + j->item, &common_one) < 0) + exit(128); + common_item = common_one->item; free_commit_list(common_one); if (!oideq(&common_item->object.oid, &j->item->object.oid)) { diff --git a/builtin/rebase.c b/builtin/rebase.c index 5b086f651a..a171e53c5e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -867,7 +867,8 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!upstream) goto done; - merge_bases = repo_get_merge_bases(the_repository, upstream, head); + if (repo_get_merge_bases(the_repository, upstream, head, &merge_bases) < 0) + exit(128); if (!merge_bases || merge_bases->next) goto done; @@ -886,8 +887,9 @@ static void fill_branch_base(struct rebase_options *options, { struct commit_list *merge_bases = NULL; - merge_bases = repo_get_merge_bases(the_repository, options->onto, - options->orig_head); + if (repo_get_merge_bases(the_repository, options->onto, + options->orig_head, &merge_bases) < 0) + exit(128); if (!merge_bases || merge_bases->next) oidcpy(branch_base, null_oid()); else diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index d08987646a..181c703d4c 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -297,7 +297,7 @@ static int try_difference(const char *arg) show_rev(NORMAL, &end_oid, end); show_rev(symmetric ? NORMAL : REVERSED, &start_oid, start); if (symmetric) { - struct commit_list *exclude; + struct commit_list *exclude = NULL; struct commit *a, *b; a = lookup_commit_reference(the_repository, &start_oid); b = lookup_commit_reference(the_repository, &end_oid); @@ -305,7 +305,8 @@ static int try_difference(const char *arg) *dotdot = '.'; return 0; } - exclude = repo_get_merge_bases(the_repository, a, b); + if (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) + exit(128); while (exclude) { struct commit *commit = pop_commit(&exclude); show_rev(REVERSED, &commit->object.oid, NULL); diff --git a/commit-reach.c b/commit-reach.c index 95cbfae0eb..ff2c5ce89f 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -188,9 +188,12 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in) 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); + struct commit_list *bases = NULL; + if (repo_get_merge_bases(the_repository, i->item, + j->item, &bases) < 0) { + free_commit_list(bases); + return NULL; + } if (!new_commits) new_commits = bases; else @@ -482,16 +485,12 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, return 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) { - struct commit_list *result = NULL; - if (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) { - free_commit_list(result); - return NULL; - } - return result; + return get_merge_bases_many_0(r, one, 1, &two, 1, result); } /* diff --git a/commit-reach.h b/commit-reach.h index 68f81549a4..2c6fcdd34f 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -9,9 +9,10 @@ struct ref_filter; struct object_id; struct object_array; -struct commit_list *repo_get_merge_bases(struct repository *r, - struct commit *rev1, - struct commit *rev2); +int repo_get_merge_bases(struct repository *r, + struct commit *rev1, + struct commit *rev2, + struct commit_list **result); struct commit_list *repo_get_merge_bases_many(struct repository *r, struct commit *one, int n, struct commit **twos); diff --git a/diff-lib.c b/diff-lib.c index 6c8df04273..5e8717c774 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -570,7 +570,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb) { int i; struct commit *mb_child[2] = {0}; - struct commit_list *merge_bases; + struct commit_list *merge_bases = NULL; for (i = 0; i < revs->pending.nr; i++) { struct object *obj = revs->pending.objects[i].item; @@ -597,7 +597,8 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb) mb_child[1] = lookup_commit_reference(the_repository, &oid); } - merge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]); + if (repo_get_merge_bases(the_repository, mb_child[0], mb_child[1], &merge_bases) < 0) + exit(128); if (!merge_bases) die(_("no merge base found")); if (merge_bases->next) diff --git a/log-tree.c b/log-tree.c index 337b9334cd..e5438b029d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1011,7 +1011,7 @@ static int do_remerge_diff(struct rev_info *opt, struct object_id *oid) { struct merge_options o; - struct commit_list *bases; + struct commit_list *bases = NULL; struct merge_result res = {0}; struct pretty_print_context ctx = {0}; struct commit *parent1 = parents->item; @@ -1036,7 +1036,8 @@ static int do_remerge_diff(struct rev_info *opt, /* Parse the relevant commits and get the merge bases */ parse_commit_or_die(parent1); parse_commit_or_die(parent2); - bases = repo_get_merge_bases(the_repository, parent1, parent2); + if (repo_get_merge_bases(the_repository, parent1, parent2, &bases) < 0) + exit(128); /* Re-merge the parents */ merge_incore_recursive(&o, bases, parent1, parent2, &res); diff --git a/merge-ort.c b/merge-ort.c index 79927954f5..033c4348e2 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -5068,7 +5068,11 @@ static void merge_ort_internal(struct merge_options *opt, struct strbuf merge_base_abbrev = STRBUF_INIT; if (!merge_bases) { - merge_bases = repo_get_merge_bases(the_repository, h1, h2); + if (repo_get_merge_bases(the_repository, h1, h2, + &merge_bases) < 0) { + result->clean = -1; + return; + } /* See merge-ort.h:merge_incore_recursive() declaration NOTE */ merge_bases = reverse_commit_list(merge_bases); } diff --git a/merge-recursive.c b/merge-recursive.c index ae23a62048..32e9d6665d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3633,7 +3633,9 @@ static int merge_recursive_internal(struct merge_options *opt, } if (!merge_bases) { - merge_bases = repo_get_merge_bases(the_repository, h1, h2); + if (repo_get_merge_bases(the_repository, h1, h2, + &merge_bases) < 0) + return -1; merge_bases = reverse_commit_list(merge_bases); } diff --git a/notes-merge.c b/notes-merge.c index 8799b522a5..51282934ae 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -607,7 +607,8 @@ int notes_merge(struct notes_merge_options *o, assert(local && remote); /* Find merge bases */ - bases = repo_get_merge_bases(the_repository, local, remote); + if (repo_get_merge_bases(the_repository, local, remote, &bases) < 0) + exit(128); if (!bases) { base_oid = null_oid(); base_tree_oid = the_hash_algo->empty_tree; diff --git a/object-name.c b/object-name.c index 3a2ef5d680..638608523d 100644 --- a/object-name.c +++ b/object-name.c @@ -1479,7 +1479,7 @@ int repo_get_oid_mb(struct repository *r, struct object_id *oid) { struct commit *one, *two; - struct commit_list *mbs; + struct commit_list *mbs = NULL; struct object_id oid_tmp; const char *dots; int st; @@ -1507,7 +1507,10 @@ int repo_get_oid_mb(struct repository *r, two = lookup_commit_reference_gently(r, &oid_tmp, 0); if (!two) return -1; - mbs = repo_get_merge_bases(r, one, two); + if (repo_get_merge_bases(r, one, two, &mbs) < 0) { + free_commit_list(mbs); + return -1; + } if (!mbs || mbs->next) st = -1; else { diff --git a/revision.c b/revision.c index 2424c9bd67..60406f365a 100644 --- a/revision.c +++ b/revision.c @@ -1963,7 +1963,7 @@ static void add_pending_commit_list(struct rev_info *revs, static void prepare_show_merge(struct rev_info *revs) { - struct commit_list *bases; + struct commit_list *bases = NULL; struct commit *head, *other; struct object_id oid; const char **prune = NULL; @@ -1978,7 +1978,8 @@ static void prepare_show_merge(struct rev_info *revs) other = lookup_commit_or_die(&oid, "MERGE_HEAD"); add_pending_object(revs, &head->object, "HEAD"); add_pending_object(revs, &other->object, "MERGE_HEAD"); - bases = repo_get_merge_bases(the_repository, head, other); + if (repo_get_merge_bases(the_repository, head, other, &bases) < 0) + exit(128); add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM); add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM); free_commit_list(bases); @@ -2066,14 +2067,17 @@ static int handle_dotdot_1(const char *arg, char *dotdot, } else { /* A...B -- find merge bases between the two */ struct commit *a, *b; - struct commit_list *exclude; + struct commit_list *exclude = NULL; a = lookup_commit_reference(revs->repo, &a_obj->oid); b = lookup_commit_reference(revs->repo, &b_obj->oid); if (!a || !b) return dotdot_missing(arg, dotdot, revs, symmetric); - exclude = repo_get_merge_bases(the_repository, a, b); + if (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) { + free_commit_list(exclude); + return -1; + } add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE, flags_exclude); add_pending_commit_list(revs, exclude, flags_exclude); diff --git a/sequencer.c b/sequencer.c index f49a871ac0..d3ca95f4fe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3908,7 +3908,7 @@ static int do_merge(struct repository *r, int run_commit_flags = 0; struct strbuf ref_name = STRBUF_INIT; struct commit *head_commit, *merge_commit, *i; - struct commit_list *bases, *j; + struct commit_list *bases = NULL, *j; struct commit_list *to_merge = NULL, **tail = &to_merge; const char *strategy = !opts->xopts.nr && (!opts->strategy || @@ -4134,7 +4134,11 @@ static int do_merge(struct repository *r, } merge_commit = to_merge->item; - bases = repo_get_merge_bases(r, head_commit, merge_commit); + if (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) { + ret = -1; + goto leave_merge; + } + if (bases && oideq(&merge_commit->object.oid, &bases->item->object.oid)) { ret = 0; diff --git a/submodule.c b/submodule.c index 213da79f66..1835728ebe 100644 --- a/submodule.c +++ b/submodule.c @@ -592,7 +592,12 @@ static void show_submodule_header(struct diff_options *o, (!is_null_oid(two) && !*right)) message = "(commits not present)"; - *merge_bases = repo_get_merge_bases(sub, *left, *right); + *merge_bases = NULL; + if (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) { + message = "(corrupt repository)"; + goto output_header; + } + if (*merge_bases) { if ((*merge_bases)->item == *left) fast_forward = 1; diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh index 12ac436873..eb02766c9a 100755 --- a/t/t4301-merge-tree-write-tree.sh +++ b/t/t4301-merge-tree-write-tree.sh @@ -945,4 +945,16 @@ test_expect_success 'check the input format when --stdin is passed' ' test_cmp expect actual ' +test_expect_success 'error out on missing commits as well' ' + git init --bare missing-commit.git && + git rev-list --objects side1 side3 >list-including-initial && + grep -v ^$(git rev-parse side1^) list && + git pack-objects missing-commit.git/objects/pack/missing-initial actual && + test_must_be_empty actual +' + test_done From f87056ce403b5572683a45efe0e9021777831894 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:15 +0000 Subject: [PATCH 09/11] commit-reach(get_octopus_merge_bases): pass on "missing commits" errors The `merge_bases_many()` function was just taught to indicate parsing errors, and now the `repo_get_merge_bases()` function (which is also surfaced via the `get_merge_bases()` macro) is aware of that, too. Naturally, the callers need to be adjusted now, too. Next step: adjust `repo_get_merge_bases_many()`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/merge-base.c | 8 ++++++-- builtin/merge.c | 6 +++++- builtin/pull.c | 5 +++-- commit-reach.c | 20 +++++++++++--------- commit-reach.h | 2 +- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index b0b3838d5f..4d1ff840bd 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -74,13 +74,17 @@ static int handle_independent(int count, const char **args) static int handle_octopus(int count, const char **args, int show_all) { struct commit_list *revs = NULL; - struct commit_list *result, *rev; + struct commit_list *result = NULL, *rev; int i; for (i = count - 1; i >= 0; i--) commit_list_insert(get_commit_reference(args[i]), &revs); - result = get_octopus_merge_bases(revs); + if (get_octopus_merge_bases(revs, &result) < 0) { + free_commit_list(revs); + free_commit_list(result); + return 128; + } free_commit_list(revs); reduce_heads_replace(&result); diff --git a/builtin/merge.c b/builtin/merge.c index 7c0189fb8f..52828edee0 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1523,7 +1523,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } else { struct commit_list *list = remoteheads; commit_list_insert(head_commit, &list); - common = get_octopus_merge_bases(list); + if (get_octopus_merge_bases(list, &common) < 0) { + free(list); + ret = 2; + goto done; + } free(list); } diff --git a/builtin/pull.c b/builtin/pull.c index 3daff0e8b9..72cbb76d52 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -815,7 +815,7 @@ static int get_octopus_merge_base(struct object_id *merge_base, const struct object_id *merge_head, const struct object_id *fork_point) { - struct commit_list *revs = NULL, *result; + struct commit_list *revs = NULL, *result = NULL; commit_list_insert(lookup_commit_reference(the_repository, curr_head), &revs); @@ -825,7 +825,8 @@ static int get_octopus_merge_base(struct object_id *merge_base, commit_list_insert(lookup_commit_reference(the_repository, fork_point), &revs); - result = get_octopus_merge_bases(revs); + if (get_octopus_merge_bases(revs, &result) < 0) + exit(128); free_commit_list(revs); reduce_heads_replace(&result); diff --git a/commit-reach.c b/commit-reach.c index ff2c5ce89f..5010fb8ad5 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -175,24 +175,26 @@ static int merge_bases_many(struct repository *r, 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) { + 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); - return NULL; + free_commit_list(*result); + *result = NULL; + return -1; } if (!new_commits) new_commits = bases; @@ -201,10 +203,10 @@ 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, diff --git a/commit-reach.h b/commit-reach.h index 2c6fcdd34f..4690b6ecd0 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -21,7 +21,7 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, struct commit *one, int n, struct commit **twos); -struct commit_list *get_octopus_merge_bases(struct commit_list *in); +int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result); int repo_is_descendant_of(struct repository *r, struct commit *commit, From 531738052158fd66bc9b65534309f5c0a9d2808d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:16 +0000 Subject: [PATCH 10/11] commit-reach(repo_get_merge_bases_many): pass on "missing commits" errors The `merge_bases_many()` function was just taught to indicate parsing errors, and now the `repo_get_merge_bases_many()` function is aware of that, too. Naturally, there are a lot of callers that need to be adjusted now, too. Next stop: `repo_get_merge_bases_dirty()`. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- bisect.c | 7 ++++--- builtin/log.c | 13 +++++++------ commit-reach.c | 16 ++++++---------- commit-reach.h | 7 ++++--- commit.c | 7 ++++--- t/helper/test-reach.c | 9 ++++++--- 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/bisect.c b/bisect.c index f75e50c339..60aae2fe50 100644 --- a/bisect.c +++ b/bisect.c @@ -836,10 +836,11 @@ static void handle_skipped_merge_base(const struct object_id *mb) static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout) { enum bisect_error res = BISECT_OK; - struct commit_list *result; + struct commit_list *result = NULL; - result = repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1, - rev + 1); + if (repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1, + rev + 1, &result) < 0) + exit(128); for (; result; result = result->next) { const struct object_id *mb = &result->item->object.oid; diff --git a/builtin/log.c b/builtin/log.c index b7147f893b..e5da0d1043 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1658,7 +1658,7 @@ static struct commit *get_base_commit(const char *base_commit, struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { - struct commit_list *base_list; + struct commit_list *base_list = NULL; struct commit *commit; struct object_id oid; @@ -1669,11 +1669,12 @@ static struct commit *get_base_commit(const char *base_commit, return NULL; } commit = lookup_commit_or_die(&oid, "upstream base"); - base_list = repo_get_merge_bases_many(the_repository, - commit, total, - list); - /* There should be one and only one merge base. */ - if (!base_list || base_list->next) { + if (repo_get_merge_bases_many(the_repository, + commit, total, + list, + &base_list) < 0 || + /* There should be one and only one merge base. */ + !base_list || base_list->next) { if (die_on_failure) { die(_("could not find exact merge base")); } else { diff --git a/commit-reach.c b/commit-reach.c index 5010fb8ad5..f19341da45 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -461,17 +461,13 @@ static int get_merge_bases_many_0(struct repository *r, 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, + int n, + struct commit **twos, + struct commit_list **result) { - struct commit_list *result = NULL; - if (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) { - free_commit_list(result); - return NULL; - } - return result; + return get_merge_bases_many_0(r, one, n, twos, 1, result); } struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, diff --git a/commit-reach.h b/commit-reach.h index 4690b6ecd0..458043f4d5 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -13,9 +13,10 @@ int repo_get_merge_bases(struct repository *r, struct commit *rev1, struct commit *rev2, struct commit_list **result); -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, int n, + struct commit **twos, + struct commit_list **result); /* To be used only when object flags after this call no longer matter */ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r, struct commit *one, int n, diff --git a/commit.c b/commit.c index ef679a0b93..467be9f7f9 100644 --- a/commit.c +++ b/commit.c @@ -1052,7 +1052,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) { struct object_id oid; struct rev_collect revs; - struct commit_list *bases; + struct commit_list *bases = NULL; int i; struct commit *ret = NULL; char *full_refname; @@ -1077,8 +1077,9 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) for (i = 0; i < revs.nr; i++) revs.commit[i]->object.flags &= ~TMP_MARK; - bases = repo_get_merge_bases_many(the_repository, commit, revs.nr, - revs.commit); + if (repo_get_merge_bases_many(the_repository, commit, revs.nr, + revs.commit, &bases) < 0) + exit(128); /* * There should be one and only one merge base, when we found diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index c96b62ff1e..1e3b431e3e 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c @@ -115,9 +115,12 @@ int cmd__reach(int ac, const char **av) else if (!strcmp(av[1], "is_descendant_of")) printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X)); else if (!strcmp(av[1], "get_merge_bases_many")) { - struct commit_list *list = repo_get_merge_bases_many(the_repository, - A, X_nr, - X_array); + struct commit_list *list = NULL; + if (repo_get_merge_bases_many(the_repository, + A, X_nr, + X_array, + &list) < 0) + exit(128); printf("%s(A,X):\n", av[1]); print_sorted_commit_ids(list); } else if (!strcmp(av[1], "reduce_heads")) { From caaf1a2942c25c1f1a15818b718c9f641e52beef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:17 +0000 Subject: [PATCH 11/11] commit-reach(repo_get_merge_bases_many_dirty): pass on errors (Actually, this commit is only about passing on "missing commits" errors, but adding that to the commit's title would have made it too long.) The `merge_bases_many()` function was just taught to indicate parsing errors, and now the `repo_get_merge_bases_many_dirty()` function is aware of that, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/merge-base.c | 9 ++++++--- commit-reach.c | 16 ++++++---------- commit-reach.h | 7 ++++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 4d1ff840bd..5a8e729502 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -10,10 +10,13 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) { - struct commit_list *result, *r; + struct commit_list *result = NULL, *r; - result = repo_get_merge_bases_many_dirty(the_repository, rev[0], - rev_nr - 1, rev + 1); + if (repo_get_merge_bases_many_dirty(the_repository, rev[0], + rev_nr - 1, rev + 1, &result) < 0) { + free_commit_list(result); + return -1; + } if (!result) return 1; diff --git a/commit-reach.c b/commit-reach.c index f19341da45..8f9b008f87 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -470,17 +470,13 @@ int repo_get_merge_bases_many(struct repository *r, 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, + int n, + struct commit **twos, + struct commit_list **result) { - struct commit_list *result = NULL; - if (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) { - free_commit_list(result); - return NULL; - } - return result; + return get_merge_bases_many_0(r, one, n, twos, 0, result); } int repo_get_merge_bases(struct repository *r, diff --git a/commit-reach.h b/commit-reach.h index 458043f4d5..bf63cc468f 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -18,9 +18,10 @@ int repo_get_merge_bases_many(struct repository *r, struct commit **twos, struct commit_list **result); /* To be used only when object flags after this call no longer matter */ -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, int n, + struct commit **twos, + struct commit_list **result); int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);