From e67431d4965d13b185db3ddb4445c3c7034ca62d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 28 Feb 2024 09:44:07 +0000 Subject: [PATCH 01/13] 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 ecc913fc99ba9e..bfa1b6454dc324 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/13] 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 bfa1b6454dc324..e81bee421a43fd 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 35c4da4948122a..68f81549a44908 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 e07b316eac3f52..b3dbdd9b5964db 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 7711798127e49e..998072d04aba22 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 1e159a754db6db..c96b62ff1e549b 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/13] 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 cfb63cce5fb9df..b3cbb7fd440486 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 92eda20683ca2d..71a195ca227315 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 3aedfd1bb6361c..3b2edddb1c4fd2 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 db1808d7c13dfd..06319228866e9e 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 d26e8fbf6f75d9..b0b3838d5f148e 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 73a68b75b06724..3daff0e8b90713 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 db656074857e76..56d8a77ed75f65 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 e81bee421a43fd..1c5121397879f1 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 12d111374107a7..33db41bfac5f97 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 8617babee41cb5..79927954f57935 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 a0c3e7a2d9105d..ae23a6204849a0 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 998072d04aba22..7ff50dd0da45e0 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/13] 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 1c5121397879f1..616738fc4e18c6 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/13] 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 616738fc4e18c6..267fcfc6caffe1 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/13] 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 267fcfc6caffe1..2585b895d3a53d 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/13] 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 2585b895d3a53d..95cbfae0eb1aa1 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/13] 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 06319228866e9e..b7147f893b4995 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 3bdec53fbe58bb..8a460d55299aa5 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 8f819781cc34a1..7c0189fb8f1999 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 5b086f651a6fce..a171e53c5e6f32 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 d08987646a0a53..181c703d4c00bc 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 95cbfae0eb1aa1..ff2c5ce89f3c64 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 68f81549a44908..2c6fcdd34f6774 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 6c8df04273702f..5e8717c774eff4 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 337b9334cdbafe..e5438b029d90f3 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 79927954f57935..033c4348e2d2b6 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 ae23a6204849a0..32e9d6665debef 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 8799b522a55f31..51282934ae62b8 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 3a2ef5d6800173..638608523ddb4d 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 2424c9bd674e53..60406f365a92b5 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 f49a871ac0666b..d3ca95f4fe0b2b 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 213da79f66116f..1835728ebe96db 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 12ac43687366d7..eb02766c9a62e5 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/13] 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 b0b3838d5f148e..4d1ff840bdbd84 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 7c0189fb8f1999..52828edee09873 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 3daff0e8b90713..72cbb76d520718 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 ff2c5ce89f3c64..5010fb8ad5ba89 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 2c6fcdd34f6774..4690b6ecd0c01e 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/13] 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 f75e50c339764d..60aae2fe50d4ed 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 b7147f893b4995..e5da0d10434ddd 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 5010fb8ad5ba89..f19341da455472 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 4690b6ecd0c01e..458043f4d58311 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 ef679a0b939046..467be9f7f99408 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 c96b62ff1e549b..1e3b431e3e7211 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/13] 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 4d1ff840bdbd84..5a8e72950298c2 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 f19341da455472..8f9b008f876787 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 458043f4d58311..bf63cc468fd311 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); From 81a34cbb2e808aa93071a924336072b9a05470eb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Mar 2024 14:09:56 +0000 Subject: [PATCH 12/13] merge-recursive: prepare for `merge_submodule()` to report errors The `merge_submodule()` function returns an integer that indicates whether the merge was clean (returning 1) or unclean (returning 0). Like the version in `merge-ort.c`, the version in `merge-recursive.c` does not report any errors (such as repository corruption) by returning -1 as of time of writing, even if the callers in `merge-ort.c` are prepared for exactly such errors. However, we want to teach (both variants of) the `merge_submodule()` function that trick: to report errors by returning -1. Therefore, prepare the caller in `merge-recursive.c` to handle that scenario. Signed-off-by: Johannes Schindelin Acked-by: Elijah Newren Signed-off-by: Junio C Hamano --- merge-recursive.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 32e9d6665debef..f3132a9ecaeb31 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1426,13 +1426,14 @@ static int merge_mode_and_contents(struct merge_options *opt, /* FIXME: bug, what if modes didn't match? */ result->clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { - result->clean = merge_submodule(opt, &result->blob.oid, - o->path, - &o->oid, - &a->oid, - &b->oid); - if (result->clean < 0) + int clean = merge_submodule(opt, &result->blob.oid, + o->path, + &o->oid, + &a->oid, + &b->oid); + if (clean < 0) return -1; + result->clean = clean; } else if (S_ISLNK(a->mode)) { switch (opt->recursive_variant) { case MERGE_VARIANT_NORMAL: From 25fd20eb44cfcbb0652595de2144f0e077a957ec Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Mar 2024 14:09:57 +0000 Subject: [PATCH 13/13] merge-ort/merge-recursive: do report errors in `merge_submodule()` In 24876ebf68b (commit-reach(repo_in_merge_bases_many): report missing commits, 2024-02-28), I taught `merge_submodule()` to handle errors reported by `repo_in_merge_bases_many()`. However, those errors were not passed through to the callers. That was unintentional, and this commit remedies that. Note that `find_first_merges()` can now also return -1 (because it passes through that return value from `repo_in_merge_bases()`), and this commit also adds the forgotten handling for that scenario. Signed-off-by: Johannes Schindelin Acked-by: Elijah Newren Signed-off-by: Junio C Hamano --- merge-ort.c | 5 +++++ merge-recursive.c | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/merge-ort.c b/merge-ort.c index 033c4348e2d2b6..5d36c04f509865 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -1819,6 +1819,7 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s " "(repository corrupt)"), path); + ret = -1; goto cleanup; } if (ret2 > 0) @@ -1829,6 +1830,7 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s " "(repository corrupt)"), path); + ret = -1; goto cleanup; } if (!ret2) { @@ -1848,6 +1850,7 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s " "(repository corrupt)"), path); + ret = -1; goto cleanup; } if (ret2 > 0) { @@ -1866,6 +1869,7 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s " "(repository corrupt)"), path); + ret = -1; goto cleanup; } if (ret2 > 0) { @@ -1899,6 +1903,7 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s " "(repository corrupt)"), path); + ret = -1; break; case 0: path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0, diff --git a/merge-recursive.c b/merge-recursive.c index f3132a9ecaeb31..fc772c2b113c94 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1246,12 +1246,14 @@ static int merge_submodule(struct merge_options *opt, ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a); if (ret2 < 0) { output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + ret = -1; 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); + ret = -1; goto cleanup; } if (!ret2) { @@ -1263,6 +1265,7 @@ static int merge_submodule(struct merge_options *opt, ret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b); if (ret2 < 0) { output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + ret = -1; goto cleanup; } if (ret2) { @@ -1281,6 +1284,7 @@ static int merge_submodule(struct merge_options *opt, ret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a); if (ret2 < 0) { output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); + ret = -1; goto cleanup; } if (ret2) { @@ -1312,6 +1316,10 @@ static int merge_submodule(struct merge_options *opt, parent_count = find_first_merges(&subrepo, &merges, path, commit_a, commit_b); switch (parent_count) { + case -1: + output(opt, 1,_("Failed to merge submodule %s (repository corrupt)"), path); + ret = -1; + break; case 0: output(opt, 1, _("Failed to merge submodule %s (merge following commits not found)"), path); break;