{"thread":{"id":"60905","subject":"[PATCH 01/12] paint_down_to_common: plug a memory leak","startedAt":"2024-02-13T08:41:52Z","lastAt":"2024-03-01T16:18:56Z","messageCount":70,"participants":["Johannes Schindelin via GitGitGadget","Junio C Hamano","Patrick Steinhardt","Johannes Schindelin","Dirk Gouders","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"488509","messageId":"818eef0a64443e42c288ea3b294bf52e7411914b.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 01/12] paint_down_to_common: plug a memory leak","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:37Z","receivedAt":"2024-02-13T08:41:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen a commit is missing, we return early (currently pretending that no\nmerge basis could be found in that case). At that stage, it is possible\nthat a merge base could have been found already, and added to the\n`result`, which is now leaked.\n\nLet's release it, to address that memory leak.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex a868a575ea1..b2b102926c9 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -105,8 +105,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tparents = parents->next;\n \t\t\tif ((p->object.flags & flags) == flags)\n \t\t\t\tcontinue;\n-\t\t\tif (repo_parse_commit(r, p))\n+\t\t\tif (repo_parse_commit(r, p)) {\n+\t\t\t\tfree_commit_list(result);\n \t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n \t\t}\n-- \ngitgitgadget\n\n"},{"id":"488510","messageId":"pull.1657.git.1707813709.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":null,"subject":"[PATCH 00/12] The merge-base logic vs missing commit objects","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:36Z","receivedAt":"2024-02-13T08:41:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series is in the same spirit as what I proposed in\nhttps://lore.kernel.org/git/pull.1651.git.1707212981.gitgitgadget@gmail.com/,\nwhere I tackled missing tree objects. This here patch series tackles missing\ncommit objects instead: Git's merge-base logic handles these missing commit\nobjects as if there had not been any commit object at all, i.e. if two\ncommits' merge base commit is missing, they will be treated as if they had\nno common commit history at all, which is a bug. Those commit objects should\nnot be missing, of course, i.e. this is only a problem in corrupt\nrepositories.\n\nThis patch series is a bit more tricky than the \"missing tree objects\" one,\nthough: The function signature of quite a few functions need to be changed\nto allow callers to discern between \"missing commit object\" vs \"no\nmerge-base found\".\n\nAnd of course it gets even more tricky because in shallow and partial clones\nwe expect commit objects to be missing, and that must not be treated like an\nerror but the existing logic is actually desirable in those scenarios.\n\nI am deeply sorry both about the length of this patch series as well as\nhaving to send it in the -rc phase.\n\nJohannes Schindelin (12):\n  paint_down_to_common: plug a memory leak\n  Let `repo_in_merge_bases()` report missing commits\n  Prepare `repo_in_merge_bases_many()` to optionally expect missing\n    commits\n  Prepare `paint_down_to_common()` for handling shallow commits\n  commit-reach: start reporting errors in `paint_down_to_common()`\n  merge_bases_many(): pass on errors from `paint_down_to_common()`\n  get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n  get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many_dirty(): pass on errors from\n    `merge_bases_many()`\n  paint_down_to_common(): special-case shallow/partial clones\n\n bisect.c                         |   7 +-\n builtin/branch.c                 |  12 +-\n builtin/fast-import.c            |   6 +-\n builtin/fetch.c                  |   2 +\n builtin/log.c                    |  30 +++--\n builtin/merge-base.c             |  18 ++-\n builtin/merge-tree.c             |   5 +-\n builtin/merge.c                  |  26 ++--\n builtin/pull.c                   |   9 +-\n builtin/rebase.c                 |   8 +-\n builtin/receive-pack.c           |   6 +-\n builtin/rev-parse.c              |   5 +-\n commit-reach.c                   | 202 +++++++++++++++++++------------\n commit-reach.h                   |  26 ++--\n commit.c                         |   7 +-\n diff-lib.c                       |   5 +-\n http-push.c                      |   5 +-\n log-tree.c                       |   5 +-\n merge-ort.c                      |  81 +++++++++++--\n merge-recursive.c                |  52 ++++++--\n notes-merge.c                    |   3 +-\n object-name.c                    |   5 +-\n remote.c                         |   2 +-\n revision.c                       |  10 +-\n sequencer.c                      |   8 +-\n shallow.c                        |  21 ++--\n submodule.c                      |   7 +-\n t/helper/test-reach.c            |  11 +-\n t/t4301-merge-tree-write-tree.sh |  12 ++\n 29 files changed, 413 insertions(+), 183 deletions(-)\n\n\nbase-commit: 564d0252ca632e0264ed670534a51d18a689ef5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1657%2Fdscho%2Fmerge-base-and-missing-objects-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1657/dscho/merge-base-and-missing-objects-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1657\n-- \ngitgitgadget\n"},{"id":"488512","messageId":"5f0af7fc0b95f9539ba7f740cb805ff1638d14cc.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 02/12] Let `repo_in_merge_bases()` report missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:38Z","receivedAt":"2024-02-13T08:41:53Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSome functions in Git's source code follow the convention that returning\na negative value indicates a fatal error, e.g. repository corruption.\n\nLet's use this convention in `repo_in_merge_bases()` to report when one\nof the specified commits is missing (i.e. when `repo_parse_commit()`\nreports an error).\n\nAlso adjust the callers of `repo_in_merge_bases()` to handle such\nnegative return values.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch.c       | 12 +++++--\n builtin/fast-import.c  |  6 +++-\n builtin/fetch.c        |  2 ++\n builtin/log.c          |  7 ++--\n builtin/merge-base.c   |  6 +++-\n builtin/pull.c         |  4 +++\n builtin/receive-pack.c |  6 +++-\n commit-reach.c         | 12 ++++---\n http-push.c            |  5 ++-\n merge-ort.c            | 75 ++++++++++++++++++++++++++++++++++++------\n merge-recursive.c      | 48 ++++++++++++++++++++++-----\n shallow.c              | 18 ++++++----\n 12 files changed, 163 insertions(+), 38 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e7ee9bd0f15..7f9e79237f3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -161,6 +161,8 @@ static int branch_merged(int kind, const char *name,\n \n \tmerged = reference_rev ? repo_in_merge_bases(the_repository, rev,\n \t\t\t\t\t\t     reference_rev) : 0;\n+\tif (merged < 0)\n+\t\texit(128);\n \n \t/*\n \t * After the safety valve is fully redefined to \"check with\n@@ -169,9 +171,13 @@ static int branch_merged(int kind, const char *name,\n \t * any of the following code, but during the transition period,\n \t * a gentle reminder is in order.\n \t */\n-\tif ((head_rev != reference_rev) &&\n-\t    (head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != merged) {\n-\t\tif (merged)\n+\tif (head_rev != reference_rev) {\n+\t\tint expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0;\n+\t\tif (expect < 0)\n+\t\t\texit(128);\n+\t\tif (expect == merged)\n+\t\t\t; /* okay */\n+\t\telse if (merged)\n \t\t\twarning(_(\"deleting branch '%s' that has been merged to\\n\"\n \t\t\t\t\"         '%s', but not yet merged to HEAD\"),\n \t\t\t\tname, reference_name);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 444f41cf8ca..14c2efa88fc 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1625,6 +1625,7 @@ static int update_branch(struct branch *b)\n \t\toidclr(&old_oid);\n \tif (!force_update && !is_null_oid(&old_oid)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n+\t\tint ret;\n \n \t\told_cmit = lookup_commit_reference_gently(the_repository,\n \t\t\t\t\t\t\t  &old_oid, 0);\n@@ -1633,7 +1634,10 @@ static int update_branch(struct branch *b)\n \t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n \n-\t\tif (!repo_in_merge_bases(the_repository, old_cmit, new_cmit)) {\n+\t\tret = repo_in_merge_bases(the_repository, old_cmit, new_cmit);\n+\t\tif (ret < 0)\n+\t\t\texit(128);\n+\t\tif (!ret) {\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, oid_to_hex(&b->oid),\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex fd134ba74d9..0584a1f8b64 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -978,6 +978,8 @@ static int update_local_ref(struct ref *ref,\n \t\tuint64_t t_before = getnanotime();\n \t\tfast_forward = repo_in_merge_bases(the_repository, current,\n \t\t\t\t\t\t   updated);\n+\t\tif (fast_forward < 0)\n+\t\t\texit(128);\n \t\tforced_updates_ms += (getnanotime() - t_before) / 1000000;\n \t} else {\n \t\tfast_forward = 1;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ba775d7b5cf..1705da71aca 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1623,7 +1623,7 @@ static struct commit *get_base_commit(const char *base_commit,\n {\n \tstruct commit *base = NULL;\n \tstruct commit **rev;\n-\tint i = 0, rev_nr = 0, auto_select, die_on_failure;\n+\tint i = 0, rev_nr = 0, auto_select, die_on_failure, ret;\n \n \tswitch (auto_base) {\n \tcase AUTO_BASE_NEVER:\n@@ -1723,7 +1723,10 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\trev_nr = DIV_ROUND_UP(rev_nr, 2);\n \t}\n \n-\tif (!repo_in_merge_bases(the_repository, base, rev[0])) {\n+\tret = repo_in_merge_bases(the_repository, base, rev[0]);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (!ret) {\n \t\tif (die_on_failure) {\n \t\t\tdie(_(\"base commit should be the ancestor of revision list\"));\n \t\t} else {\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex e68b7fe45d7..0308fd73289 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -103,12 +103,16 @@ static int handle_octopus(int count, const char **args, int show_all)\n static int handle_is_ancestor(int argc, const char **argv)\n {\n \tstruct commit *one, *two;\n+\tint ret;\n \n \tif (argc != 2)\n \t\tdie(\"--is-ancestor takes exactly two commits\");\n \tone = get_commit_reference(argv[0]);\n \ttwo = get_commit_reference(argv[1]);\n-\tif (repo_in_merge_bases(the_repository, one, two))\n+\tret = repo_in_merge_bases(the_repository, one, two);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (ret)\n \t\treturn 0;\n \telse\n \t\treturn 1;\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex be2b2c9ebc9..e6f2942c0c5 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -931,6 +931,8 @@ static int get_can_ff(struct object_id *orig_head,\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n \tret = repo_is_descendant_of(the_repository, merge_head, list);\n \tfree_commit_list(list);\n+\tif (ret < 0)\n+\t\texit(128);\n \treturn ret;\n }\n \n@@ -955,6 +957,8 @@ static int already_up_to_date(struct object_id *orig_head,\n \t\tcommit_list_insert(theirs, &list);\n \t\tok = repo_is_descendant_of(the_repository, ours, list);\n \t\tfree_commit_list(list);\n+\t\tif (ok < 0)\n+\t\t\texit(128);\n \t\tif (!ok)\n \t\t\treturn 0;\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8c4f0cb90a9..956fea6293e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1546,6 +1546,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t    starts_with(name, \"refs/heads/\")) {\n \t\tstruct object *old_object, *new_object;\n \t\tstruct commit *old_commit, *new_commit;\n+\t\tint ret2;\n \n \t\told_object = parse_object(the_repository, old_oid);\n \t\tnew_object = parse_object(the_repository, new_oid);\n@@ -1559,7 +1560,10 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t}\n \t\told_commit = (struct commit *)old_object;\n \t\tnew_commit = (struct commit *)new_object;\n-\t\tif (!repo_in_merge_bases(the_repository, old_commit, new_commit)) {\n+\t\tret2 = repo_in_merge_bases(the_repository, old_commit, new_commit);\n+\t\tif (ret2 < 0)\n+\t\t\texit(128);\n+\t\tif (!ret2) {\n \t\t\trp_error(\"denying non-fast-forward %s\"\n \t\t\t\t \" (you should pull first)\", name);\n \t\t\tret = \"non-fast-forward\";\ndiff --git a/commit-reach.c b/commit-reach.c\nindex b2b102926c9..dab32eb470d 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -463,11 +463,13 @@ int repo_is_descendant_of(struct repository *r,\n \t} else {\n \t\twhile (with_commit) {\n \t\t\tstruct commit *other;\n+\t\t\tint ret;\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n-\t\t\t\treturn 1;\n+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n \t\t}\n \t\treturn 0;\n \t}\n@@ -484,10 +486,10 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n \tif (repo_parse_commit(r, commit))\n-\t\treturn ret;\n+\t\treturn -1;\n \tfor (i = 0; i < nr_reference; i++) {\n \t\tif (repo_parse_commit(r, reference[i]))\n-\t\t\treturn ret;\n+\t\t\treturn -1;\n \n \t\tgeneration = commit_graph_generation(reference[i]);\n \t\tif (generation > max_generation)\n@@ -596,6 +598,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)\n \tcommit_list_insert(old_commit, &old_commit_list);\n \tret = repo_is_descendant_of(the_repository,\n \t\t\t\t    new_commit, old_commit_list);\n+\tif (ret < 0)\n+\t\texit(128);\n \tfree_commit_list(old_commit_list);\n \treturn ret;\n }\ndiff --git a/http-push.c b/http-push.c\nindex a704f490fdb..85fa2f457d4 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1576,8 +1576,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote)\n \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n \t\t\t\t\t\t     remote->name);\n+\tint i = repo_in_merge_bases(the_repository, branch, head);\n \n-\treturn repo_in_merge_bases(the_repository, branch, head);\n+\tif (i < 0)\n+\t\texit(128);\n+\treturn i;\n }\n \n static int delete_remote_branch(const char *pattern, int force)\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 6491070d965..64e76afe89f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -544,6 +544,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n \tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n+\tCONFLICT_SUBMODULE_CORRUPT,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -596,7 +597,9 @@ static const char *type_short_descriptions[] = {\n \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n \t\t\"CONFLICT (submodule may have rewinds)\",\n \t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n-\t\t\"CONFLICT (submodule lacks merge base)\"\n+\t\t\"CONFLICT (submodule lacks merge base)\",\n+\t[CONFLICT_SUBMODULE_CORRUPT] =\n+\t\t\"CONFLICT (submodule corrupt)\"\n };\n \n struct logical_conflict_info {\n@@ -1710,7 +1713,11 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t\tif (ret > 0)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1725,9 +1732,14 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1749,7 +1761,7 @@ static int merge_submodule(struct merge_options *opt,\n {\n \tstruct repository subrepo;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1796,8 +1808,26 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n+\tif (ret2 < 1) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n+\tif (ret2 < 1) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s \"\n@@ -1807,7 +1837,16 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, b);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1816,7 +1855,16 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, a);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1841,6 +1889,13 @@ static int merge_submodule(struct merge_options *opt,\n \tparent_count = find_first_merges(&subrepo, path, commit_a, commit_b,\n \t\t\t\t\t &merges);\n \tswitch (parent_count) {\n+\tcase -1:\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tbreak;\n \tcase 0:\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n \t\t\t path, NULL, NULL, NULL,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3beb0801b1..e3fe7803cbe 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1144,7 +1144,10 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t\tif (ret)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1159,9 +1162,14 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1197,7 +1205,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   const struct object_id *b)\n {\n \tstruct repository subrepo;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_base, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1234,14 +1242,29 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (commits don't follow merge-base)\"), path);\n \t\tgoto cleanup;\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, b);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1254,7 +1277,12 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, a);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1402,6 +1430,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&o->oid,\n \t\t\t\t\t\t\t&a->oid,\n \t\t\t\t\t\t\t&b->oid);\n+\t\t\tif (result->clean < 0)\n+\t\t\t\treturn -1;\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n \t\t\tcase MERGE_VARIANT_NORMAL:\ndiff --git a/shallow.c b/shallow.c\nindex ac728cdd778..cf4b95114b7 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -795,12 +795,16 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tif (!*bitmap)\n \t\t\tcontinue;\n \t\tfor (j = 0; j < bitmap_nr; j++)\n-\t\t\tif (bitmap[0][j] &&\n-\t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n-\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n-\t\t\t\tdst++;\n-\t\t\t\tbreak;\n+\t\t\tif (bitmap[0][j]) {\n+\t\t\t\t/* Step 7, reachability test at commit level */\n+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\texit(128);\n+\t\t\t\tif (!ret) {\n+\t\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n+\t\t\t\t\tdst++;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t}\n \tinfo->nr_ours = dst;\n@@ -829,6 +833,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\t\t\t\t\t\t    commit,\n \t\t\t\t\t\t\t    si->nr_commits,\n \t\t\t\t\t\t\t    si->commits);\n+\t\tif (si->reachable[c] < 0)\n+\t\t\texit(128);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\n-- \ngitgitgadget\n\n"},{"id":"488511","messageId":"8395c3efbc3c34d732347f743a0263707ec454bf.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 03/12] Prepare `repo_in_merge_bases_many()` to optionally expect missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:39Z","receivedAt":"2024-02-13T08:41:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nCurrently this function treats unrelated commit histories the same way\nas commit histories with missing commit objects.\n\nTypically, missing commit objects constitute a corrupt repository,\nthough, and should be reported as such. The next commits will make it\nso, but there is one exception: In `git fetch --update-shallow` we\n_expect_ commit objects to be missing, and we do want to treat the\nnow-incomplete commit histories as unrelated.\n\nTo allow for that, let's introduce an additional parameter that is\npassed to `repo_in_merge_bases_many()` to trigger this behavior, and use\nit in the two callers in `shallow.c`.\n\nThis does not change behavior in this commit, but prepares in an\neasy-to-review way for the upcoming changes that will make the merge\nbase logic more stringent with regards to missing commit objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c        | 9 +++++----\n commit-reach.h        | 3 ++-\n remote.c              | 2 +-\n shallow.c             | 5 +++--\n t/helper/test-reach.c | 2 +-\n 5 files changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex dab32eb470d..248a0f2b39d 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -467,7 +467,7 @@ int repo_is_descendant_of(struct repository *r,\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit);\n+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n \t\t}\n@@ -479,17 +479,18 @@ int repo_is_descendant_of(struct repository *r,\n  * Is \"commit\" an ancestor of one of the \"references\"?\n  */\n int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference)\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits)\n {\n \tstruct commit_list *bases;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n \tif (repo_parse_commit(r, commit))\n-\t\treturn -1;\n+\t\treturn ignore_missing_commits ? 0 : -1;\n \tfor (i = 0; i < nr_reference; i++) {\n \t\tif (repo_parse_commit(r, reference[i]))\n-\t\t\treturn -1;\n+\t\t\treturn ignore_missing_commits ? 0 : -1;\n \n \t\tgeneration = commit_graph_generation(reference[i]);\n \t\tif (generation > max_generation)\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 35c4da49481..68f81549a44 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -30,7 +30,8 @@ int repo_in_merge_bases(struct repository *r,\n \t\t\tstruct commit *reference);\n int repo_in_merge_bases_many(struct repository *r,\n \t\t\t     struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference);\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits);\n \n /*\n  * Takes a list of commits and returns a new list where those\ndiff --git a/remote.c b/remote.c\nindex abb24822beb..763c80f4a7d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2675,7 +2675,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n \t\tif (MERGE_BASES_BATCH_SIZE < size)\n \t\t\tsize = MERGE_BASES_BATCH_SIZE;\n \n-\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))\n+\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk, 0)))\n \t\t\tbreak;\n \t}\n \ndiff --git a/shallow.c b/shallow.c\nindex cf4b95114b7..f71496f35c3 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -797,7 +797,7 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tfor (j = 0; j < bitmap_nr; j++)\n \t\t\tif (bitmap[0][j]) {\n \t\t\t\t/* Step 7, reachability test at commit level */\n-\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits);\n+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n \t\t\t\tif (ret < 0)\n \t\t\t\t\texit(128);\n \t\t\t\tif (!ret) {\n@@ -832,7 +832,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\tsi->reachable[c] = repo_in_merge_bases_many(the_repository,\n \t\t\t\t\t\t\t    commit,\n \t\t\t\t\t\t\t    si->nr_commits,\n-\t\t\t\t\t\t\t    si->commits);\n+\t\t\t\t\t\t\t    si->commits,\n+\t\t\t\t\t\t\t    1);\n \t\tif (si->reachable[c] < 0)\n \t\t\texit(128);\n \t\tsi->need_reachability_test[c] = 0;\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 3e173399a00..aa816e168ea 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -113,7 +113,7 @@ int cmd__reach(int ac, const char **av)\n \t\t       repo_in_merge_bases(the_repository, A, B));\n \telse if (!strcmp(av[1], \"in_merge_bases_many\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1],\n-\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array));\n+\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-- \ngitgitgadget\n\n"},{"id":"488514","messageId":"7477b18adebe4eca721a0f2fa73ec5bdec389d36.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 04/12] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:40Z","receivedAt":"2024-02-13T08:41:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen `git fetch --update-shallow` needs to test for commit ancestry, it\ncan naturally run into a missing object (e.g. if it is a parent of a\nshallow commit). To accommodate, the merge base logic will need to be\nable to handle this situation gracefully.\n\nCurrently, that logic pretends that a missing parent commit is\nequivalent to a a missing parent commit, and for the purpose of\n`--update-shallow` that is exactly what we need it to do.\n\nTherefore, let's introduce a flag to indicate when that is precisely the\nlogic we want.\n\nWe need a flag, and cannot rely on `is_repository_shallow()` to indicate\nthat situation, because that function would return 0: There may not\nactually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\nshallow root with receive.updateshallow on\") and t5538.4 (\"add new\nshallow root with receive.updateshallow on\").\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 248a0f2b39d..1d1d8c989de 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -53,7 +53,8 @@ static int queue_has_nonstale(struct prio_queue *queue)\n static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t\t\tstruct commit *one, int n,\n \t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation)\n+\t\t\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\t\t\tint ignore_missing_commits)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n \tstruct commit_list *result = NULL;\n@@ -106,6 +107,13 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tif ((p->object.flags & flags) == flags)\n \t\t\t\tcontinue;\n \t\t\tif (repo_parse_commit(r, p)) {\n+\t\t\t\t/*\n+\t\t\t\t * At this stage, we know that the commit is\n+\t\t\t\t * missing: `repo_parse_commit()` uses\n+\t\t\t\t * `OBJECT_INFO_DIE_IF_CORRUPT` and therefore\n+\t\t\t\t * corrupt commits would already have been\n+\t\t\t\t * dispatched with a `die()`.\n+\t\t\t\t */\n \t\t\t\tfree_commit_list(result);\n \t\t\t\treturn NULL;\n \t\t\t}\n@@ -142,7 +150,7 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0);\n+\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -213,7 +221,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n \t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation);\n+\t\t\t\t\t      work, min_generation, 0);\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -503,7 +511,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \n \tbases = paint_down_to_common(r, commit,\n \t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation);\n+\t\t\t\t     generation, ignore_missing_commits);\n \tif (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n-- \ngitgitgadget\n\n"},{"id":"488513","messageId":"f8aedb89f1ca5973a6565dc57af7b6c658b0b44f.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 05/12] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:41Z","receivedAt":"2024-02-13T08:41:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIf a commit cannot be parsed, it is currently ignored when looking for\nmerge bases. That's undesirable as the operation can pretend success in\na corrupt repository, even though the command should fail with an error\nmessage.\n\nLet's start at the bottom of the stack by teaching the\n`paint_down_to_common()` function to return an `int`: if negative, it\nindicates fatal error, if 0 success.\n\nThis requires a couple of callers to be adjusted accordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 56 +++++++++++++++++++++++++++++++-------------------\n 1 file changed, 35 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 1d1d8c989de..dafe117036b 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -50,14 +50,14 @@ static int queue_has_nonstale(struct prio_queue *queue)\n }\n \n /* all input commits in one and twos[] must have been parsed! */\n-static struct commit_list *paint_down_to_common(struct repository *r,\n-\t\t\t\t\t\tstruct commit *one, int n,\n-\t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation,\n-\t\t\t\t\t\tint ignore_missing_commits)\n+static int paint_down_to_common(struct repository *r,\n+\t\t\t\tstruct commit *one, int n,\n+\t\t\t\tstruct commit **twos,\n+\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\tint ignore_missing_commits,\n+\t\t\t\tstruct commit_list **result)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n-\tstruct commit_list *result = NULL;\n \tint i;\n \ttimestamp_t last_gen = GENERATION_NUMBER_INFINITY;\n \n@@ -66,8 +66,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \n \tone->object.flags |= PARENT1;\n \tif (!n) {\n-\t\tcommit_list_append(one, &result);\n-\t\treturn result;\n+\t\tcommit_list_append(one, result);\n+\t\treturn 0;\n \t}\n \tprio_queue_put(&queue, one);\n \n@@ -95,7 +95,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\tif (flags == (PARENT1 | PARENT2)) {\n \t\t\tif (!(commit->object.flags & RESULT)) {\n \t\t\t\tcommit->object.flags |= RESULT;\n-\t\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\t\tcommit_list_insert_by_date(commit, result);\n \t\t\t}\n \t\t\t/* Mark parents of a found merge stale */\n \t\t\tflags |= STALE;\n@@ -114,8 +114,11 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t * corrupt commits would already have been\n \t\t\t\t * dispatched with a `die()`.\n \t\t\t\t */\n-\t\t\t\tfree_commit_list(result);\n-\t\t\t\treturn NULL;\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\tif (ignore_missing_commits)\n+\t\t\t\t\treturn 0;\n+\t\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t\t     oid_to_hex(&p->object.oid));\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n@@ -123,7 +126,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t}\n \n \tclear_prio_queue(&queue);\n-\treturn result;\n+\treturn 0;\n }\n \n static struct commit_list *merge_bases_many(struct repository *r,\n@@ -150,7 +153,8 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0)\n+\t\treturn NULL;\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -204,7 +208,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \tfor (i = 0; i < cnt; i++)\n \t\trepo_parse_commit(r, array[i]);\n \tfor (i = 0; i < cnt; i++) {\n-\t\tstruct commit_list *common;\n+\t\tstruct commit_list *common = NULL;\n \t\ttimestamp_t min_generation = commit_graph_generation(array[i]);\n \n \t\tif (redundant[i])\n@@ -220,8 +224,9 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\tif (curr_generation < min_generation)\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n-\t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation, 0);\n+\t\tif (paint_down_to_common(r, array[i], filled,\n+\t\t\t\t\t work, min_generation, 0, &common))\n+\t\t\treturn -1;\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -421,6 +426,10 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tclear_commit_marks_many(n, twos, all_flags);\n \n \tcnt = remove_redundant(r, rslt, cnt);\n+\tif (cnt < 0) {\n+\t\tfree(rslt);\n+\t\treturn NULL;\n+\t}\n \tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n \t\tcommit_list_insert_by_date(rslt[i], &result);\n@@ -490,7 +499,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \t\t\t     int nr_reference, struct commit **reference,\n \t\t\t     int ignore_missing_commits)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n@@ -509,10 +518,11 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \tif (generation > max_generation)\n \t\treturn ret;\n \n-\tbases = paint_down_to_common(r, commit,\n-\t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation, ignore_missing_commits);\n-\tif (commit->object.flags & PARENT2)\n+\tif (paint_down_to_common(r, commit,\n+\t\t\t\t nr_reference, reference,\n+\t\t\t\t generation, ignore_missing_commits, &bases))\n+\t\tret = -1;\n+\telse if (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n \tclear_commit_marks_many(nr_reference, reference, all_flags);\n@@ -565,6 +575,10 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \t\t}\n \t}\n \tnum_head = remove_redundant(the_repository, array, num_head);\n+\tif (num_head < 0) {\n+\t\tfree(array);\n+\t\treturn NULL;\n+\t}\n \tfor (i = 0; i < num_head; i++)\n \t\ttail = &commit_list_insert(array[i], tail)->next;\n \tfree(array);\n-- \ngitgitgadget\n\n"},{"id":"488515","messageId":"0aca08a2cb5d679dc4d5dd9401f989d026c80b5b.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 06/12] merge_bases_many(): pass on errors from `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:42Z","receivedAt":"2024-02-13T08:41:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `paint_down_to_common()` function was just taught to indicate\nparsing errors, and now the `merge_bases_many()` function is aware of\nthat, too.\n\nOne tricky aspect is that `merge_bases_many()` parses commits of its\nown, but wants to gracefully handle the scenario where NULL is passed as\na merge head, returning the empty list of merge bases. The way this was\nhandled involved calling `repo_parse_commit(NULL)` and relying on it to\nreturn an error. This has to be done differently now so that we can\nhandle missing commits correctly by producing a fatal error.\n\nNext step: adjust the caller of `merge_bases_many()`:\n`get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex dafe117036b..c9969da8c6c 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -129,39 +129,47 @@ static int paint_down_to_common(struct repository *r,\n \treturn 0;\n }\n \n-static struct commit_list *merge_bases_many(struct repository *r,\n-\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t    struct commit **twos)\n+static int merge_bases_many(struct repository *r,\n+\t\t\t    struct commit *one, int n,\n+\t\t\t    struct commit **twos,\n+\t\t\t    struct commit_list **result)\n {\n \tstruct commit_list *list = NULL;\n-\tstruct commit_list *result = NULL;\n \tint i;\n \n \tfor (i = 0; i < n; i++) {\n-\t\tif (one == twos[i])\n+\t\tif (one == twos[i]) {\n \t\t\t/*\n \t\t\t * We do not mark this even with RESULT so we do not\n \t\t\t * have to clean it up.\n \t\t\t */\n-\t\t\treturn commit_list_insert(one, &result);\n+\t\t\t*result = commit_list_insert(one, result);\n+\t\t\treturn 0;\n+\t\t}\n \t}\n \n+\tif (!one)\n+\t\treturn 0;\n \tif (repo_parse_commit(r, one))\n-\t\treturn NULL;\n+\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t     oid_to_hex(&one->object.oid));\n \tfor (i = 0; i < n; i++) {\n+\t\tif (!twos[i])\n+\t\t\treturn 0;\n \t\tif (repo_parse_commit(r, twos[i]))\n-\t\t\treturn NULL;\n+\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n \t}\n \n \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0)\n-\t\treturn NULL;\n+\t\treturn -1;\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n \t\tif (!(commit->object.flags & STALE))\n-\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\tcommit_list_insert_by_date(commit, result);\n \t}\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n@@ -399,10 +407,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tresult = merge_bases_many(r, one, n, twos);\n+\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n+\t\treturn NULL;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n \t\t\treturn result;\n-- \ngitgitgadget\n\n"},{"id":"488516","messageId":"03734cc09dab415f05c5524b2a1e3492fefad8c2.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 07/12] get_merge_bases_many_0(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:43Z","receivedAt":"2024-02-13T08:41:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate\nparsing errors, and now the `get_merge_bases_many_0()` function is aware\nof that, too.\n\nNext step: adjust the callers of `get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 57 +++++++++++++++++++++++++++++++-------------------\n 1 file changed, 36 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex c9969da8c6c..359853275a9 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -399,37 +399,38 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt\n \treturn remove_redundant_no_gen(r, array, cnt);\n }\n \n-static struct commit_list *get_merge_bases_many_0(struct repository *r,\n-\t\t\t\t\t\t  struct commit *one,\n-\t\t\t\t\t\t  int n,\n-\t\t\t\t\t\t  struct commit **twos,\n-\t\t\t\t\t\t  int cleanup)\n+static int get_merge_bases_many_0(struct repository *r,\n+\t\t\t\t  struct commit *one,\n+\t\t\t\t  int n,\n+\t\t\t\t  struct commit **twos,\n+\t\t\t\t  int cleanup,\n+\t\t\t\t  struct commit_list **result)\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n-\t\treturn NULL;\n+\tif (merge_bases_many(r, one, n, twos, result) < 0)\n+\t\treturn -1;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n-\t\t\treturn result;\n+\t\t\treturn 0;\n \t}\n-\tif (!result || !result->next) {\n+\tif (!*result || !(*result)->next) {\n \t\tif (cleanup) {\n \t\t\tclear_commit_marks(one, all_flags);\n \t\t\tclear_commit_marks_many(n, twos, all_flags);\n \t\t}\n-\t\treturn result;\n+\t\treturn 0;\n \t}\n \n \t/* There are more than one */\n-\tcnt = commit_list_count(result);\n+\tcnt = commit_list_count(*result);\n \tCALLOC_ARRAY(rslt, cnt);\n-\tfor (list = result, i = 0; list; list = list->next)\n+\tfor (list = *result, i = 0; list; list = list->next)\n \t\trslt[i++] = list->item;\n-\tfree_commit_list(result);\n+\tfree_commit_list(*result);\n+\t*result = NULL;\n \n \tclear_commit_marks(one, all_flags);\n \tclear_commit_marks_many(n, twos, all_flags);\n@@ -437,13 +438,12 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tcnt = remove_redundant(r, rslt, cnt);\n \tif (cnt < 0) {\n \t\tfree(rslt);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n-\tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n-\t\tcommit_list_insert_by_date(rslt[i], &result);\n+\t\tcommit_list_insert_by_date(rslt[i], result);\n \tfree(rslt);\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n@@ -451,7 +451,12 @@ struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      int n,\n \t\t\t\t\t      struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n@@ -459,14 +464,24 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    int n,\n \t\t\t\t\t\t    struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 0);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases(struct repository *r,\n \t\t\t\t\t struct commit *one,\n \t\t\t\t\t struct commit *two)\n {\n-\treturn get_merge_bases_many_0(r, one, 1, &two, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"488517","messageId":"43c33e2eac4e5addc039ede6e3943de4cab4faee.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 08/12] repo_get_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:44Z","receivedAt":"2024-02-13T08:41:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `repo_get_merge_bases()` macro) is aware of that, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext step: adjust the callers of `get_octopus_merge_bases()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/log.c                    | 10 +++++-----\n builtin/merge-tree.c             |  5 +++--\n builtin/merge.c                  | 20 ++++++++++++--------\n builtin/rebase.c                 |  8 +++++---\n builtin/rev-parse.c              |  5 +++--\n commit-reach.c                   | 23 +++++++++++------------\n commit-reach.h                   |  7 ++++---\n diff-lib.c                       |  5 +++--\n log-tree.c                       |  5 +++--\n merge-ort.c                      |  6 +++++-\n merge-recursive.c                |  4 +++-\n notes-merge.c                    |  3 ++-\n object-name.c                    |  5 +++--\n revision.c                       | 10 ++++++----\n sequencer.c                      |  8 ++++++--\n submodule.c                      |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 12 ++++++++++++\n 17 files changed, 92 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 1705da71aca..befafd6ae04 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1702,11 +1702,11 @@ static struct commit *get_base_commit(const char *base_commit,\n \t */\n \twhile (rev_nr > 1) {\n \t\tfor (i = 0; i < rev_nr / 2; i++) {\n-\t\t\tstruct commit_list *merge_base;\n-\t\t\tmerge_base = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  rev[2 * i],\n-\t\t\t\t\t\t\t  rev[2 * i + 1]);\n-\t\t\tif (!merge_base || merge_base->next) {\n+\t\t\tstruct commit_list *merge_base = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository,\n+\t\t\t\t\t\t rev[2 * i],\n+\t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n+\t\t\t    !merge_base || merge_base->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex a35e0452d66..76200250629 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -463,8 +463,9 @@ static int real_merge(struct merge_tree_options *o,\n \t\t * Get the merge bases, in reverse order; see comment above\n \t\t * merge_incore_recursive in merge-ort.h\n \t\t */\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, parent1,\n-\t\t\t\t\t\t   parent2);\n+\t\tif (repo_get_merge_bases(the_repository, parent1,\n+\t\t\t\t\t parent2, &merge_bases) < 0)\n+\t\t\texit(128);\n \t\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d748d46e135..ac9d58adc29 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1517,10 +1517,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (!remoteheads)\n \t\t; /* already up-to-date */\n-\telse if (!remoteheads->next)\n-\t\tcommon = repo_get_merge_bases(the_repository, head_commit,\n-\t\t\t\t\t      remoteheads->item);\n-\telse {\n+\telse if (!remoteheads->next) {\n+\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t remoteheads->item, &common) < 0) {\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n+\t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n \t\tcommon = get_octopus_merge_bases(list);\n@@ -1631,7 +1634,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tstruct commit_list *j;\n \n \t\tfor (j = remoteheads; j; j = j->next) {\n-\t\t\tstruct commit_list *common_one;\n+\t\t\tstruct commit_list *common_one = NULL;\n \t\t\tstruct commit *common_item;\n \n \t\t\t/*\n@@ -1639,9 +1642,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t * merge_bases again, otherwise \"git merge HEAD^\n \t\t\t * HEAD^^\" would be missed.\n \t\t\t */\n-\t\t\tcommon_one = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  head_commit,\n-\t\t\t\t\t\t\t  j->item);\n+\t\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t\t j->item, &common_one) < 0)\n+\t\t\t\texit(128);\n+\n \t\t\tcommon_item = common_one->item;\n \t\t\tfree_commit_list(common_one);\n \t\t\tif (!oideq(&common_item->object.oid, &j->item->object.oid)) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 043c65dccd9..06a55fc7325 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -879,7 +879,8 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!upstream)\n \t\tgoto done;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, upstream, head);\n+\tif (repo_get_merge_bases(the_repository, upstream, head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\tgoto done;\n \n@@ -898,8 +899,9 @@ static void fill_branch_base(struct rebase_options *options,\n {\n \tstruct commit_list *merge_bases = NULL;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, options->onto,\n-\t\t\t\t\t   options->orig_head);\n+\tif (repo_get_merge_bases(the_repository, options->onto,\n+\t\t\t\t options->orig_head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\toidcpy(branch_base, null_oid());\n \telse\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex fde8861ca4e..c97d0f6144c 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -297,7 +297,7 @@ static int try_difference(const char *arg)\n \t\tshow_rev(NORMAL, &end_oid, end);\n \t\tshow_rev(symmetric ? NORMAL : REVERSED, &start_oid, start);\n \t\tif (symmetric) {\n-\t\t\tstruct commit_list *exclude;\n+\t\t\tstruct commit_list *exclude = NULL;\n \t\t\tstruct commit *a, *b;\n \t\t\ta = lookup_commit_reference(the_repository, &start_oid);\n \t\t\tb = lookup_commit_reference(the_repository, &end_oid);\n@@ -305,7 +305,8 @@ static int try_difference(const char *arg)\n \t\t\t\t*dotdot = '.';\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n+\t\t\t\texit(128);\n \t\t\twhile (exclude) {\n \t\t\t\tstruct commit *commit = pop_commit(&exclude);\n \t\t\t\tshow_rev(REVERSED, &commit->object.oid, NULL);\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 359853275a9..3471c933f6c 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -185,9 +185,12 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n \t\tfor (j = ret; j; j = j->next) {\n-\t\t\tstruct commit_list *bases;\n-\t\t\tbases = repo_get_merge_bases(the_repository, i->item,\n-\t\t\t\t\t\t     j->item);\n+\t\t\tstruct commit_list *bases = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository, i->item,\n+\t\t\t\t\t\t j->item, &bases) < 0) {\n+\t\t\t\tfree_commit_list(bases);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n \t\t\telse\n@@ -472,16 +475,12 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \treturn result;\n }\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *one,\n-\t\t\t\t\t struct commit *two)\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *one,\n+\t\t\t struct commit *two,\n+\t\t\t struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, 1, &two, 1, result);\n }\n \n /*\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 68f81549a44..2c6fcdd34f6 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -9,9 +9,10 @@ struct ref_filter;\n struct object_id;\n struct object_array;\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *rev1,\n-\t\t\t\t\t struct commit *rev2);\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *rev1,\n+\t\t\t struct commit *rev2,\n+\t\t\t struct commit_list **result);\n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      struct commit *one, int n,\n \t\t\t\t\t      struct commit **twos);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 0e9ec4f68af..498224ccce2 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -565,7 +565,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n {\n \tint i;\n \tstruct commit *mb_child[2] = {0};\n-\tstruct commit_list *merge_bases;\n+\tstruct commit_list *merge_bases = NULL;\n \n \tfor (i = 0; i < revs->pending.nr; i++) {\n \t\tstruct object *obj = revs->pending.objects[i].item;\n@@ -592,7 +592,8 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n \t\tmb_child[1] = lookup_commit_reference(the_repository, &oid);\n \t}\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]);\n+\tif (repo_get_merge_bases(the_repository, mb_child[0], mb_child[1], &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases)\n \t\tdie(_(\"no merge base found\"));\n \tif (merge_bases->next)\ndiff --git a/log-tree.c b/log-tree.c\nindex 504da6b519e..4f337766a39 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1010,7 +1010,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \t\t\t   struct object_id *oid)\n {\n \tstruct merge_options o;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct merge_result res = {0};\n \tstruct pretty_print_context ctx = {0};\n \tstruct commit *parent1 = parents->item;\n@@ -1035,7 +1035,8 @@ static int do_remerge_diff(struct rev_info *opt,\n \t/* Parse the relevant commits and get the merge bases */\n \tparse_commit_or_die(parent1);\n \tparse_commit_or_die(parent2);\n-\tbases = repo_get_merge_bases(the_repository, parent1, parent2);\n+\tif (repo_get_merge_bases(the_repository, parent1, parent2, &bases) < 0)\n+\t\texit(128);\n \n \t/* Re-merge the parents */\n \tmerge_incore_recursive(&o, bases, parent1, parent2, &res);\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 64e76afe89f..88f23a37f5a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5060,7 +5060,11 @@ static void merge_ort_internal(struct merge_options *opt,\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tresult->clean = -1;\n+\t\t\treturn;\n+\t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3fe7803cbe..0bed0b97424 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3632,7 +3632,9 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0)\n+\t\t\treturn -1;\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 8799b522a55..51282934ae6 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -607,7 +607,8 @@ int notes_merge(struct notes_merge_options *o,\n \tassert(local && remote);\n \n \t/* Find merge bases */\n-\tbases = repo_get_merge_bases(the_repository, local, remote);\n+\tif (repo_get_merge_bases(the_repository, local, remote, &bases) < 0)\n+\t\texit(128);\n \tif (!bases) {\n \t\tbase_oid = null_oid();\n \t\tbase_tree_oid = the_hash_algo->empty_tree;\ndiff --git a/object-name.c b/object-name.c\nindex 0bfa29dbbfe..89c34f9b49e 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1481,7 +1481,7 @@ int repo_get_oid_mb(struct repository *r,\n \t\t    struct object_id *oid)\n {\n \tstruct commit *one, *two;\n-\tstruct commit_list *mbs;\n+\tstruct commit_list *mbs = NULL;\n \tstruct object_id oid_tmp;\n \tconst char *dots;\n \tint st;\n@@ -1509,7 +1509,8 @@ int repo_get_oid_mb(struct repository *r,\n \ttwo = lookup_commit_reference_gently(r, &oid_tmp, 0);\n \tif (!two)\n \t\treturn -1;\n-\tmbs = repo_get_merge_bases(r, one, two);\n+\tif (repo_get_merge_bases(r, one, two, &mbs) < 0)\n+\t\treturn -1;\n \tif (!mbs || mbs->next)\n \t\tst = -1;\n \telse {\ndiff --git a/revision.c b/revision.c\nindex 00d5c29bfce..3492cc95159 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1965,7 +1965,7 @@ static void add_pending_commit_list(struct rev_info *revs,\n \n static void prepare_show_merge(struct rev_info *revs)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct commit *head, *other;\n \tstruct object_id oid;\n \tconst char **prune = NULL;\n@@ -1980,7 +1980,8 @@ static void prepare_show_merge(struct rev_info *revs)\n \tother = lookup_commit_or_die(&oid, \"MERGE_HEAD\");\n \tadd_pending_object(revs, &head->object, \"HEAD\");\n \tadd_pending_object(revs, &other->object, \"MERGE_HEAD\");\n-\tbases = repo_get_merge_bases(the_repository, head, other);\n+\tif (repo_get_merge_bases(the_repository, head, other, &bases) < 0)\n+\t\texit(128);\n \tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);\n \tadd_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);\n \tfree_commit_list(bases);\n@@ -2068,14 +2069,15 @@ static int handle_dotdot_1(const char *arg, char *dotdot,\n \t} else {\n \t\t/* A...B -- find merge bases between the two */\n \t\tstruct commit *a, *b;\n-\t\tstruct commit_list *exclude;\n+\t\tstruct commit_list *exclude = NULL;\n \n \t\ta = lookup_commit_reference(revs->repo, &a_obj->oid);\n \t\tb = lookup_commit_reference(revs->repo, &b_obj->oid);\n \t\tif (!a || !b)\n \t\t\treturn dotdot_missing(arg, dotdot, revs, symmetric);\n \n-\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n+\t\t\treturn -1;\n \t\tadd_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,\n \t\t\t\t     flags_exclude);\n \t\tadd_pending_commit_list(revs, exclude, flags_exclude);\ndiff --git a/sequencer.c b/sequencer.c\nindex d584cac8ed9..4417f2f1956 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3913,7 +3913,7 @@ static int do_merge(struct repository *r,\n \tint run_commit_flags = 0;\n \tstruct strbuf ref_name = STRBUF_INIT;\n \tstruct commit *head_commit, *merge_commit, *i;\n-\tstruct commit_list *bases, *j;\n+\tstruct commit_list *bases = NULL, *j;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts.nr &&\n \t\t(!opts->strategy ||\n@@ -4139,7 +4139,11 @@ static int do_merge(struct repository *r,\n \t}\n \n \tmerge_commit = to_merge->item;\n-\tbases = repo_get_merge_bases(r, head_commit, merge_commit);\n+\tif (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n \tif (bases && oideq(&merge_commit->object.oid,\n \t\t\t   &bases->item->object.oid)) {\n \t\tret = 0;\ndiff --git a/submodule.c b/submodule.c\nindex e603a19a876..04931a5474b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -595,7 +595,12 @@ static void show_submodule_header(struct diff_options *o,\n \t     (!is_null_oid(two) && !*right))\n \t\tmessage = \"(commits not present)\";\n \n-\t*merge_bases = repo_get_merge_bases(sub, *left, *right);\n+\t*merge_bases = NULL;\n+\tif (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) {\n+\t\tmessage = \"(corrupt repository)\";\n+\t\tgoto output_header;\n+\t}\n+\n \tif (*merge_bases) {\n \t\tif ((*merge_bases)->item == *left)\n \t\t\tfast_forward = 1;\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex b2c8a43fce3..5d1e7aca4c8 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -945,4 +945,16 @@ test_expect_success 'check the input format when --stdin is passed' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out on missing commits as well' '\n+\tgit init --bare missing-commit.git &&\n+\tgit rev-list --objects side1 side3 >list-including-initial &&\n+\tgrep -v ^$(git rev-parse side1^) <list-including-initial >list &&\n+\tgit pack-objects missing-commit.git/objects/pack/missing-initial <list &&\n+\tside1=$(git rev-parse side1) &&\n+\tside3=$(git rev-parse side3) &&\n+\ttest_must_fail git --git-dir=missing-commit.git \\\n+\t\tmerge-tree --allow-unrelated-histories $side1 $side3 >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"488518","messageId":"7fbf660e371454b309d962624d5121c87aed1e85.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 09/12] get_octopus_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:45Z","receivedAt":"2024-02-13T08:42:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `get_merge_bases()` macro) is aware of that, too.\n\nNaturally, the callers need to be adjusted now, too.\n\nNext step: adjust `repo_get_merge_bases_many()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  5 +++--\n builtin/merge.c      |  6 +++++-\n builtin/pull.c       |  5 +++--\n commit-reach.c       | 20 +++++++++++---------\n commit-reach.h       |  2 +-\n 5 files changed, 23 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 0308fd73289..6faabfb6698 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -77,13 +77,14 @@ static int handle_independent(int count, const char **args)\n static int handle_octopus(int count, const char **args, int show_all)\n {\n \tstruct commit_list *revs = NULL;\n-\tstruct commit_list *result, *rev;\n+\tstruct commit_list *result = NULL, *rev;\n \tint i;\n \n \tfor (i = count - 1; i >= 0; i--)\n \t\tcommit_list_insert(get_commit_reference(args[i]), &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0)\n+\t\treturn 128;\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ac9d58adc29..94c5b693972 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1526,7 +1526,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n-\t\tcommon = get_octopus_merge_bases(list);\n+\t\tif (get_octopus_merge_bases(list, &common) < 0) {\n+\t\t\tfree(list);\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n \t\tfree(list);\n \t}\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e6f2942c0c5..0c5a55f2f4d 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -820,7 +820,7 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tconst struct object_id *merge_head,\n \t\tconst struct object_id *fork_point)\n {\n-\tstruct commit_list *revs = NULL, *result;\n+\tstruct commit_list *revs = NULL, *result = NULL;\n \n \tcommit_list_insert(lookup_commit_reference(the_repository, curr_head),\n \t\t\t   &revs);\n@@ -830,7 +830,8 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tcommit_list_insert(lookup_commit_reference(the_repository, fork_point),\n \t\t\t\t   &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0)\n+\t\texit(128);\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/commit-reach.c b/commit-reach.c\nindex 3471c933f6c..1b618eb9cd1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -172,24 +172,26 @@ static int merge_bases_many(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)\n {\n-\tstruct commit_list *i, *j, *k, *ret = NULL;\n+\tstruct commit_list *i, *j, *k;\n \n \tif (!in)\n-\t\treturn ret;\n+\t\treturn 0;\n \n-\tcommit_list_insert(in->item, &ret);\n+\tcommit_list_insert(in->item, result);\n \n \tfor (i = in->next; i; i = i->next) {\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n-\t\tfor (j = ret; j; j = j->next) {\n+\t\tfor (j = *result; j; j = j->next) {\n \t\t\tstruct commit_list *bases = NULL;\n \t\t\tif (repo_get_merge_bases(the_repository, i->item,\n \t\t\t\t\t\t j->item, &bases) < 0) {\n \t\t\t\tfree_commit_list(bases);\n-\t\t\t\treturn NULL;\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n+\t\t\t\treturn -1;\n \t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n@@ -198,10 +200,10 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\t\tfor (k = bases; k; k = k->next)\n \t\t\t\tend = k;\n \t\t}\n-\t\tfree_commit_list(ret);\n-\t\tret = new_commits;\n+\t\tfree_commit_list(*result);\n+\t\t*result = new_commits;\n \t}\n-\treturn ret;\n+\treturn 0;\n }\n \n static int remove_redundant_no_gen(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 2c6fcdd34f6..4690b6ecd0c 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -21,7 +21,7 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\n \t\t\t\t\t\t    struct commit **twos);\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in);\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n int repo_is_descendant_of(struct repository *r,\n \t\t\t  struct commit *commit,\n-- \ngitgitgadget\n\n"},{"id":"488519","messageId":"55757c3a35d73c7fd81ec7fb19bb4ba6093e325b.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 10/12] repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:46Z","receivedAt":"2024-02-13T08:42:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many()` function is aware of\nthat, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext stop: `repo_get_merge_bases_dirty()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n bisect.c              |  7 ++++---\n builtin/log.c         | 13 +++++++------\n commit-reach.c        | 16 ++++++----------\n commit-reach.h        |  7 ++++---\n commit.c              |  7 ++++---\n t/helper/test-reach.c |  9 ++++++---\n 6 files changed, 31 insertions(+), 28 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 1be8e0a2711..2018466d69f 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -851,10 +851,11 @@ static void handle_skipped_merge_base(const struct object_id *mb)\n static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)\n {\n \tenum bisect_error res = BISECT_OK;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \n-\tresult = repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n-\t\t\t\t\t   rev + 1);\n+\tif (repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n+\t\t\t\t      rev + 1, &result) < 0)\n+\t\texit(128);\n \n \tfor (; result; result = result->next) {\n \t\tconst struct object_id *mb = &result->item->object.oid;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex befafd6ae04..c75790a7cec 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1656,7 +1656,7 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\tstruct branch *curr_branch = branch_get(NULL);\n \t\tconst char *upstream = branch_get_upstream(curr_branch, NULL);\n \t\tif (upstream) {\n-\t\t\tstruct commit_list *base_list;\n+\t\t\tstruct commit_list *base_list = NULL;\n \t\t\tstruct commit *commit;\n \t\t\tstruct object_id oid;\n \n@@ -1667,11 +1667,12 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tcommit = lookup_commit_or_die(&oid, \"upstream base\");\n-\t\t\tbase_list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t      commit, total,\n-\t\t\t\t\t\t\t      list);\n-\t\t\t/* There should be one and only one merge base. */\n-\t\t\tif (!base_list || base_list->next) {\n+\t\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t\t      commit, total,\n+\t\t\t\t\t\t      list,\n+\t\t\t\t\t\t      &base_list) < 0 ||\n+\t\t\t    /* There should be one and only one merge base. */\n+\t\t\t    !base_list || base_list->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"could not find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 1b618eb9cd1..f0006ab6422 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -451,17 +451,13 @@ static int get_merge_bases_many_0(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one,\n-\t\t\t\t\t      int n,\n-\t\t\t\t\t      struct commit **twos)\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one,\n+\t\t\t      int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 4690b6ecd0c..458043f4d58 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -13,9 +13,10 @@ int repo_get_merge_bases(struct repository *r,\n \t\t\t struct commit *rev1,\n \t\t\t struct commit *rev2,\n \t\t\t struct commit_list **result);\n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one, int n,\n-\t\t\t\t\t      struct commit **twos);\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one, int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\ndiff --git a/commit.c b/commit.c\nindex 8405d7c3fce..00add5d81c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1054,7 +1054,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n {\n \tstruct object_id oid;\n \tstruct rev_collect revs;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint i;\n \tstruct commit *ret = NULL;\n \tchar *full_refname;\n@@ -1079,8 +1079,9 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n \tfor (i = 0; i < revs.nr; i++)\n \t\trevs.commit[i]->object.flags &= ~TMP_MARK;\n \n-\tbases = repo_get_merge_bases_many(the_repository, commit, revs.nr,\n-\t\t\t\t\t  revs.commit);\n+\tif (repo_get_merge_bases_many(the_repository, commit, revs.nr,\n+\t\t\t\t      revs.commit, &bases) < 0)\n+\t\texit(128);\n \n \t/*\n \t * There should be one and only one merge base, when we found\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex aa816e168ea..84ee9da8681 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -117,9 +117,12 @@ int cmd__reach(int ac, const char **av)\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-\t\tstruct commit_list *list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t\t     A, X_nr,\n-\t\t\t\t\t\t\t\t     X_array);\n+\t\tstruct commit_list *list = NULL;\n+\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t      A, X_nr,\n+\t\t\t\t\t      X_array,\n+\t\t\t\t\t      &list) < 0)\n+\t\t\texit(128);\n \t\tprintf(\"%s(A,X):\\n\", av[1]);\n \t\tprint_sorted_commit_ids(list);\n \t} else if (!strcmp(av[1], \"reduce_heads\")) {\n-- \ngitgitgadget\n\n"},{"id":"488520","messageId":"e7fcc96196ca87bfe2331f8bd570fcb60c262587.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 11/12] repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:47Z","receivedAt":"2024-02-13T08:42:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many_dirty()` function is\naware of that, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  7 ++++---\n commit-reach.c       | 16 ++++++----------\n commit-reach.h       |  7 ++++---\n 3 files changed, 14 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 6faabfb6698..2b6af1bc35b 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -13,10 +13,11 @@\n \n static int show_merge_base(struct commit **rev, int rev_nr, int show_all)\n {\n-\tstruct commit_list *result, *r;\n+\tstruct commit_list *result = NULL, *r;\n \n-\tresult = repo_get_merge_bases_many_dirty(the_repository, rev[0],\n-\t\t\t\t\t\t rev_nr - 1, rev + 1);\n+\tif (repo_get_merge_bases_many_dirty(the_repository, rev[0],\n+\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0)\n+\t\treturn -1;\n \n \tif (!result)\n \t\treturn 1;\ndiff --git a/commit-reach.c b/commit-reach.c\nindex f0006ab6422..25b39c302a8 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -460,17 +460,13 @@ int repo_get_merge_bases_many(struct repository *r,\n \treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one,\n-\t\t\t\t\t\t    int n,\n-\t\t\t\t\t\t    struct commit **twos)\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one,\n+\t\t\t\t    int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 0, result);\n }\n \n int repo_get_merge_bases(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 458043f4d58..bf63cc468fd 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -18,9 +18,10 @@ int repo_get_merge_bases_many(struct repository *r,\n \t\t\t      struct commit **twos,\n \t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t\t    struct commit **twos);\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one, int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result);\n \n int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n-- \ngitgitgadget\n\n"},{"id":"488521","messageId":"33894600ae77156ba022ebba6cec4f2b24c77cd2.1707813709.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH 12/12] paint_down_to_common(): special-case shallow/partial clones","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-13T08:41:48Z","receivedAt":"2024-02-13T08:42:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIn shallow/partial clones, we _expect_ commits to be missing. Let's\nteach the merge-base logic to ignore those and simply go ahead and\ntreat the involved commit histories as cut off at that point.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 25b39c302a8..4af60c2501d 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -10,6 +10,8 @@\n #include \"tag.h\"\n #include \"commit-reach.h\"\n #include \"ewah/ewok.h\"\n+#include \"shallow.h\"\n+#include \"promisor-remote.h\"\n \n /* Remember to update object flag allocation in object.h */\n #define PARENT1\t\t(1u<<16)\n@@ -115,7 +117,9 @@ static int paint_down_to_common(struct repository *r,\n \t\t\t\t * dispatched with a `die()`.\n \t\t\t\t */\n \t\t\t\tfree_commit_list(*result);\n-\t\t\t\tif (ignore_missing_commits)\n+\t\t\t\tif (ignore_missing_commits ||\n+\t\t\t\t    is_repository_shallow(r) ||\n+\t\t\t\t    repo_has_promisor_remote(r))\n \t\t\t\t\treturn 0;\n \t\t\t\treturn error(_(\"could not parse commit %s\"),\n \t\t\t\t\t     oid_to_hex(&p->object.oid));\n-- \ngitgitgadget\n"},{"id":"488562","messageId":"xmqqplx05hyd.fsf@gitster.g","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] The merge-base logic vs missing commit objects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-13T18:37:14Z","receivedAt":"2024-02-13T18:37:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> I am deeply sorry both about the length of this patch series as well as\n> having to send it in the -rc phase.\n\nYou do not have to ;-), but thanks.\n\nAlso outside the scope of -rc, there were some untied loose ends in\nthe missing tree thing before we can move to a new topic, if I\nrecall correctly.\n\nSpeaking of -rc, the project would be helped by your expertise a lot\nif you gave a quick Ack on the make_relative() thing [*].\n\n\n[Reference]\n\n * f6628636 (unit-tests: do show relative file paths on non-Windows,\n   too, 2024-02-11)\n   https://lore.kernel.org/git/xmqqle7r9enn.fsf_-_@gitster.g/\n"},{"id":"488713","messageId":"Zc3aWzeRXFm1E2O_@tanuki","threadId":"60905","inReplyTo":"5f0af7fc0b95f9539ba7f740cb805ff1638d14cc.1707813709.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/12] Let `repo_in_merge_bases()` report missing commits","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-02-15T09:33:15Z","receivedAt":"2024-02-15T09:33:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 13, 2024 at 08:41:38AM +0000, Johannes Schindelin via GitGitGadget wrote:\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n[snip]\n> diff --git a/http-push.c b/http-push.c\n> index a704f490fdb..85fa2f457d4 100644\n> --- a/http-push.c\n> +++ b/http-push.c\n> @@ -1576,8 +1576,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote)\n>  \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n>  \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n>  \t\t\t\t\t\t     remote->name);\n> +\tint i = repo_in_merge_bases(the_repository, branch, head);\n>  \n> -\treturn repo_in_merge_bases(the_repository, branch, head);\n> +\tif (i < 0)\n> +\t\texit(128);\n> +\treturn i;\n\nNit: it's a bit unusual that we use `i` here instead of `ret`. Not worth\na reroll on its own though.\n\n>  }\n>  \n>  static int delete_remote_branch(const char *pattern, int force)\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 6491070d965..64e76afe89f 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -544,6 +544,7 @@ enum conflict_and_info_types {\n>  \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n>  \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n>  \tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n> +\tCONFLICT_SUBMODULE_CORRUPT,\n>  \n>  \t/* Keep this entry _last_ in the list */\n>  \tNB_CONFLICT_TYPES,\n> @@ -596,7 +597,9 @@ static const char *type_short_descriptions[] = {\n>  \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n>  \t\t\"CONFLICT (submodule may have rewinds)\",\n>  \t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n> -\t\t\"CONFLICT (submodule lacks merge base)\"\n> +\t\t\"CONFLICT (submodule lacks merge base)\",\n> +\t[CONFLICT_SUBMODULE_CORRUPT] =\n> +\t\t\"CONFLICT (submodule corrupt)\"\n>  };\n>  \n>  struct logical_conflict_info {\n> @@ -1710,7 +1713,11 @@ static int find_first_merges(struct repository *repo,\n>  \t\tdie(\"revision walk setup failed\");\n>  \twhile ((commit = get_revision(&revs)) != NULL) {\n>  \t\tstruct object *o = &(commit->object);\n> -\t\tif (repo_in_merge_bases(repo, b, commit))\n> +\t\tint ret = repo_in_merge_bases(repo, b, commit);\n> +\n> +\t\tif (ret < 0)\n> +\t\t\treturn ret;\n\nThis is leaking both `merges` and `revs`.\n\n> +\t\tif (ret > 0)\n>  \t\t\tadd_object_array(o, NULL, &merges);\n>  \t}\n>  \treset_revision_walk();\n> @@ -1725,9 +1732,14 @@ static int find_first_merges(struct repository *repo,\n>  \t\tcontains_another = 0;\n>  \t\tfor (j = 0; j < merges.nr; j++) {\n>  \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n> -\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n> -\t\t\t\tcontains_another = 1;\n> -\t\t\t\tbreak;\n> +\t\t\tif (i != j) {\n> +\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n> +\t\t\t\tif (ret < 0)\n> +\t\t\t\t\treturn ret;\n> +\t\t\t\tif (ret > 0) {\n> +\t\t\t\t\tcontains_another = 1;\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n>  \t\t\t}\n>  \t\t}\n>  \n> @@ -1749,7 +1761,7 @@ static int merge_submodule(struct merge_options *opt,\n>  {\n>  \tstruct repository subrepo;\n>  \tstruct strbuf sb = STRBUF_INIT;\n> -\tint ret = 0;\n> +\tint ret = 0, ret2;\n>  \tstruct commit *commit_o, *commit_a, *commit_b;\n>  \tint parent_count;\n>  \tstruct object_array merges;\n> @@ -1796,8 +1808,26 @@ static int merge_submodule(struct merge_options *opt,\n>  \t}\n>  \n>  \t/* check whether both changes are forward */\n> -\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n> -\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n> +\tif (ret2 < 1) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s \"\n> +\t\t\t   \"(repository corrupt)\"),\n> +\t\t\t path);\n> +\t\tgoto cleanup;\n> +\t}\n\nIs `if (ret2 < 1)` intentional? I doubt it is, because it would also\ntrigger for `ret2 == 0`, which we try to explicitly handle in the next\nline. So the following condition `if (!ret2)` cannot ever be true.\n\n> +\tif (!ret2)\n> +\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n> +\tif (ret2 < 1) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s \"\n> +\t\t\t   \"(repository corrupt)\"),\n> +\t\t\t path);\n> +\t\tgoto cleanup;\n> +\t}\n\nSame here.\n\n> +\tif (!ret2) {\n>  \t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n>  \t\t\t path, NULL, NULL, NULL,\n>  \t\t\t _(\"Failed to merge submodule %s \"\n> @@ -1807,7 +1837,16 @@ static int merge_submodule(struct merge_options *opt,\n>  \t}\n>  \n>  \t/* Case #1: a is contained in b or vice versa */\n> -\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n> +\tif (ret2 < 0) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s \"\n> +\t\t\t   \"(repository corrupt)\"),\n> +\t\t\t path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (ret2 > 0) {\n>  \t\toidcpy(result, b);\n>  \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n>  \t\t\t path, NULL, NULL, NULL,\n> @@ -1816,7 +1855,16 @@ static int merge_submodule(struct merge_options *opt,\n>  \t\tret = 1;\n>  \t\tgoto cleanup;\n>  \t}\n> -\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n> +\tif (ret2 < 0) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s \"\n> +\t\t\t   \"(repository corrupt)\"),\n> +\t\t\t path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (ret2 > 0) {\n>  \t\toidcpy(result, a);\n>  \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n>  \t\t\t path, NULL, NULL, NULL,\n> @@ -1841,6 +1889,13 @@ static int merge_submodule(struct merge_options *opt,\n>  \tparent_count = find_first_merges(&subrepo, path, commit_a, commit_b,\n>  \t\t\t\t\t &merges);\n>  \tswitch (parent_count) {\n> +\tcase -1:\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s \"\n> +\t\t\t   \"(repository corrupt)\"),\n> +\t\t\t path);\n> +\t\tbreak;\n\nI was wondering whether it is safe to `break` here because we do end up\ncalling `object_array_clear()` on `merges` which we didn't initialize in\nthis function. But `find_first_merges()` always initializes the array\nwith zeroes, so this is fine.\n\n>  \tcase 0:\n>  \t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n>  \t\t\t path, NULL, NULL, NULL,\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index e3beb0801b1..e3fe7803cbe 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -1144,7 +1144,10 @@ static int find_first_merges(struct repository *repo,\n>  \t\tdie(\"revision walk setup failed\");\n>  \twhile ((commit = get_revision(&revs)) != NULL) {\n>  \t\tstruct object *o = &(commit->object);\n> -\t\tif (repo_in_merge_bases(repo, b, commit))\n> +\t\tint ret = repo_in_merge_bases(repo, b, commit);\n> +\t\tif (ret < 0)\n> +\t\t\treturn ret;\n\nWe also leak `merges` and `revs` here.\n\nPatrick\n\n> +\t\tif (ret)\n>  \t\t\tadd_object_array(o, NULL, &merges);\n>  \t}\n>  \treset_revision_walk();\n> @@ -1159,9 +1162,14 @@ static int find_first_merges(struct repository *repo,\n>  \t\tcontains_another = 0;\n>  \t\tfor (j = 0; j < merges.nr; j++) {\n>  \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n> -\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n> -\t\t\t\tcontains_another = 1;\n> -\t\t\t\tbreak;\n> +\t\t\tif (i != j) {\n> +\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n> +\t\t\t\tif (ret < 0)\n> +\t\t\t\t\treturn ret;\n> +\t\t\t\tif (ret > 0) {\n> +\t\t\t\t\tcontains_another = 1;\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n>  \t\t\t}\n>  \t\t}\n>  \n> @@ -1197,7 +1205,7 @@ static int merge_submodule(struct merge_options *opt,\n>  \t\t\t   const struct object_id *b)\n>  {\n>  \tstruct repository subrepo;\n> -\tint ret = 0;\n> +\tint ret = 0, ret2;\n>  \tstruct commit *commit_base, *commit_a, *commit_b;\n>  \tint parent_count;\n>  \tstruct object_array merges;\n> @@ -1234,14 +1242,29 @@ static int merge_submodule(struct merge_options *opt,\n>  \t}\n>  \n>  \t/* check whether both changes are forward */\n> -\tif (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||\n> -\t    !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);\n> +\tif (ret2 < 0) {\n> +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (ret2)\n> +\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n> +\tif (ret2 < 0) {\n> +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (!ret2) {\n>  \t\toutput(opt, 1, _(\"Failed to merge submodule %s (commits don't follow merge-base)\"), path);\n>  \t\tgoto cleanup;\n>  \t}\n>  \n>  \t/* Case #1: a is contained in b or vice versa */\n> -\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n> +\tif (ret2 < 0) {\n> +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (ret2) {\n>  \t\toidcpy(result, b);\n>  \t\tif (show(opt, 3)) {\n>  \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n> @@ -1254,7 +1277,12 @@ static int merge_submodule(struct merge_options *opt,\n>  \t\tret = 1;\n>  \t\tgoto cleanup;\n>  \t}\n> -\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n> +\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n> +\tif (ret2 < 0) {\n> +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (ret2) {\n>  \t\toidcpy(result, a);\n>  \t\tif (show(opt, 3)) {\n>  \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n> @@ -1402,6 +1430,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n>  \t\t\t\t\t\t\t&o->oid,\n>  \t\t\t\t\t\t\t&a->oid,\n>  \t\t\t\t\t\t\t&b->oid);\n> +\t\t\tif (result->clean < 0)\n> +\t\t\t\treturn -1;\n>  \t\t} else if (S_ISLNK(a->mode)) {\n>  \t\t\tswitch (opt->recursive_variant) {\n>  \t\t\tcase MERGE_VARIANT_NORMAL:\n> diff --git a/shallow.c b/shallow.c\n> index ac728cdd778..cf4b95114b7 100644\n> --- a/shallow.c\n> +++ b/shallow.c\n> @@ -795,12 +795,16 @@ static void post_assign_shallow(struct shallow_info *info,\n>  \t\tif (!*bitmap)\n>  \t\t\tcontinue;\n>  \t\tfor (j = 0; j < bitmap_nr; j++)\n> -\t\t\tif (bitmap[0][j] &&\n> -\t\t\t    /* Step 7, reachability test at commit level */\n> -\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n> -\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n> -\t\t\t\tdst++;\n> -\t\t\t\tbreak;\n> +\t\t\tif (bitmap[0][j]) {\n> +\t\t\t\t/* Step 7, reachability test at commit level */\n> +\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits);\n> +\t\t\t\tif (ret < 0)\n> +\t\t\t\t\texit(128);\n> +\t\t\t\tif (!ret) {\n> +\t\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n> +\t\t\t\t\tdst++;\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n>  \t\t\t}\n>  \t}\n>  \tinfo->nr_ours = dst;\n> @@ -829,6 +833,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n>  \t\t\t\t\t\t\t    commit,\n>  \t\t\t\t\t\t\t    si->nr_commits,\n>  \t\t\t\t\t\t\t    si->commits);\n> +\t\tif (si->reachable[c] < 0)\n> +\t\t\texit(128);\n>  \t\tsi->need_reachability_test[c] = 0;\n>  \t}\n>  \treturn si->reachable[c];\n> -- \n> gitgitgadget\n> \n> \n"},{"id":"489134","messageId":"6e4e409cd43bcf445f811e171958ef4a169a0163.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 01/11] paint_down_to_common: plug two memory leaks","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:40Z","receivedAt":"2024-02-22T13:21:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen a commit is missing, we return early (currently pretending that no\nmerge basis could be found in that case). At that stage, it is possible\nthat a merge base could have been found already, and added to the\n`result`, which is now leaked.\n\nThe priority queue has a similar issue: There might still be a commit in\nthat queue.\n\nLet's release both, to address the potential memory leaks.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex a868a575ea1..7ea916f9ebd 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -105,8 +105,11 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tparents = parents->next;\n \t\t\tif ((p->object.flags & flags) == flags)\n \t\t\t\tcontinue;\n-\t\t\tif (repo_parse_commit(r, p))\n+\t\t\tif (repo_parse_commit(r, p)) {\n+\t\t\t\tclear_prio_queue(&queue);\n+\t\t\t\tfree_commit_list(result);\n \t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n \t\t}\n-- \ngitgitgadget\n\n"},{"id":"489135","messageId":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.git.1707813709.gitgitgadget@gmail.com","subject":"[PATCH v2 00/11] The merge-base logic vs missing commit objects","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:39Z","receivedAt":"2024-02-22T13:21:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series is in the same spirit as what I proposed in\nhttps://lore.kernel.org/git/pull.1651.git.1707212981.gitgitgadget@gmail.com/,\nwhere I tackled missing tree objects. This here patch series tackles missing\ncommit objects instead: Git's merge-base logic handles these missing commit\nobjects as if there had not been any commit object at all, i.e. if two\ncommits' merge base commit is missing, they will be treated as if they had\nno common commit history at all, which is a bug. Those commit objects should\nnot be missing, of course, i.e. this is only a problem in corrupt\nrepositories.\n\nThis patch series is a bit more tricky than the \"missing tree objects\" one,\nthough: The function signature of quite a few functions need to be changed\nto allow callers to discern between \"missing commit object\" vs \"no\nmerge-base found\".\n\nAnd of course it gets even more tricky because in shallow and partial clones\nwe expect commit objects to be missing, and that must not be treated like an\nerror but the existing logic is actually desirable in those scenarios.\n\nI am deeply sorry both about the length of this patch series as well as\nhaving to lean so heavily on reviews on the Git mailing list.\n\nChanges since v1:\n\n * Addressed a lot of memory leaks.\n * Reordered patch 2 and 3 to render the commit message's comment about\n   unchanged behavior true.\n * Fixed the incorrectly converted condition in the merge_submodule()\n   function.\n * The last patch (\"paint_down_to_common(): special-case shallow/partial\n   clones\") was dropped because it is not actually necessary, and the\n   explanation for that was added to the commit message of \"Prepare\n   paint_down_to_common() for handling shallow commits\".\n * An inconsistently-named variable i was renamed to be consistent with the\n   other variables called ret.\n\nJohannes Schindelin (11):\n  paint_down_to_common: plug two memory leaks\n  Prepare `repo_in_merge_bases_many()` to optionally expect missing\n    commits\n  Start reporting missing commits in `repo_in_merge_bases_many()`\n  Prepare `paint_down_to_common()` for handling shallow commits\n  commit-reach: start reporting errors in `paint_down_to_common()`\n  merge_bases_many(): pass on errors from `paint_down_to_common()`\n  get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n  get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many_dirty(): pass on errors from\n    `merge_bases_many()`\n\n bisect.c                         |   7 +-\n builtin/branch.c                 |  12 +-\n builtin/fast-import.c            |   6 +-\n builtin/fetch.c                  |   2 +\n builtin/log.c                    |  30 +++--\n builtin/merge-base.c             |  23 +++-\n builtin/merge-tree.c             |   5 +-\n builtin/merge.c                  |  26 ++--\n builtin/pull.c                   |   9 +-\n builtin/rebase.c                 |   8 +-\n builtin/receive-pack.c           |   6 +-\n builtin/rev-parse.c              |   5 +-\n commit-reach.c                   | 209 ++++++++++++++++++++-----------\n commit-reach.h                   |  26 ++--\n commit.c                         |   7 +-\n diff-lib.c                       |   5 +-\n http-push.c                      |   5 +-\n log-tree.c                       |   5 +-\n merge-ort.c                      |  87 +++++++++++--\n merge-recursive.c                |  58 +++++++--\n notes-merge.c                    |   3 +-\n object-name.c                    |   7 +-\n remote.c                         |   2 +-\n revision.c                       |  12 +-\n sequencer.c                      |   8 +-\n shallow.c                        |  21 ++--\n submodule.c                      |   7 +-\n t/helper/test-reach.c            |  11 +-\n t/t4301-merge-tree-write-tree.sh |  12 ++\n 29 files changed, 441 insertions(+), 183 deletions(-)\n\n\nbase-commit: 564d0252ca632e0264ed670534a51d18a689ef5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1657%2Fdscho%2Fmerge-base-and-missing-objects-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1657/dscho/merge-base-and-missing-objects-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1657\n\nRange-diff vs v1:\n\n  1:  818eef0a644 !  1:  6e4e409cd43 paint_down_to_common: plug a memory leak\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    paint_down_to_common: plug a memory leak\n     +    paint_down_to_common: plug two memory leaks\n      \n          When a commit is missing, we return early (currently pretending that no\n          merge basis could be found in that case). At that stage, it is possible\n          that a merge base could have been found already, and added to the\n          `result`, which is now leaked.\n      \n     -    Let's release it, to address that memory leak.\n     +    The priority queue has a similar issue: There might still be a commit in\n     +    that queue.\n     +\n     +    Let's release both, to address the potential memory leaks.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repositor\n       \t\t\t\tcontinue;\n      -\t\t\tif (repo_parse_commit(r, p))\n      +\t\t\tif (repo_parse_commit(r, p)) {\n     ++\t\t\t\tclear_prio_queue(&queue);\n      +\t\t\t\tfree_commit_list(result);\n       \t\t\t\treturn NULL;\n      +\t\t\t}\n  3:  8395c3efbc3 !  2:  5c16becfb9b Prepare `repo_in_merge_bases_many()` to optionally expect missing commits\n     @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n       \n       \t\t\tother = with_commit->item;\n       \t\t\twith_commit = with_commit->next;\n     --\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit);\n     -+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n     - \t\t\tif (ret)\n     - \t\t\t\treturn ret;\n     +-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n     ++\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n     + \t\t\t\treturn 1;\n       \t\t}\n     + \t\treturn 0;\n      @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n        * Is \"commit\" an ancestor of one of the \"references\"?\n        */\n     @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n       {\n       \tstruct commit_list *bases;\n       \tint ret = 0, i;\n     - \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n     - \n     - \tif (repo_parse_commit(r, commit))\n     --\t\treturn -1;\n     -+\t\treturn ignore_missing_commits ? 0 : -1;\n     - \tfor (i = 0; i < nr_reference; i++) {\n     - \t\tif (repo_parse_commit(r, reference[i]))\n     --\t\t\treturn -1;\n     -+\t\t\treturn ignore_missing_commits ? 0 : -1;\n     - \n     - \t\tgeneration = commit_graph_generation(reference[i]);\n     - \t\tif (generation > max_generation)\n      \n       ## commit-reach.h ##\n      @@ commit-reach.h: int repo_in_merge_bases(struct repository *r,\n     @@ remote.c: static int is_reachable_in_reflog(const char *local, const struct ref\n       ## shallow.c ##\n      @@ shallow.c: static void post_assign_shallow(struct shallow_info *info,\n       \t\tfor (j = 0; j < bitmap_nr; j++)\n     - \t\t\tif (bitmap[0][j]) {\n     - \t\t\t\t/* Step 7, reachability test at commit level */\n     --\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits);\n     -+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n     - \t\t\t\tif (ret < 0)\n     - \t\t\t\t\texit(128);\n     - \t\t\t\tif (!ret) {\n     + \t\t\tif (bitmap[0][j] &&\n     + \t\t\t    /* Step 7, reachability test at commit level */\n     +-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n     ++\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n     + \t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n     + \t\t\t\tdst++;\n     + \t\t\t\tbreak;\n      @@ shallow.c: int delayed_reachability_test(struct shallow_info *si, int c)\n       \t\tsi->reachable[c] = repo_in_merge_bases_many(the_repository,\n       \t\t\t\t\t\t\t    commit,\n     @@ shallow.c: int delayed_reachability_test(struct shallow_info *si, int c)\n      -\t\t\t\t\t\t\t    si->commits);\n      +\t\t\t\t\t\t\t    si->commits,\n      +\t\t\t\t\t\t\t    1);\n     - \t\tif (si->reachable[c] < 0)\n     - \t\t\texit(128);\n       \t\tsi->need_reachability_test[c] = 0;\n     + \t}\n     + \treturn si->reachable[c];\n      \n       ## t/helper/test-reach.c ##\n      @@ t/helper/test-reach.c: int cmd__reach(int ac, const char **av)\n  2:  5f0af7fc0b9 !  3:  4dd214f91d4 Let `repo_in_merge_bases()` report missing commits\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    Let `repo_in_merge_bases()` report missing commits\n     +    Start reporting missing commits in `repo_in_merge_bases_many()`\n      \n          Some functions in Git's source code follow the convention that returning\n          a negative value indicates a fatal error, e.g. repository corruption.\n     @@ Commit message\n          Also adjust the callers of `repo_in_merge_bases()` to handle such\n          negative return values.\n      \n     +    Note: As of this patch, errors are returned only if any of the specified\n     +    merge heads is missing. Over the course of the next patches, missing\n     +    commits will also be reported by the `paint_down_to_common()` function,\n     +    which is called by `repo_in_merge_bases_many()`, and those errors will\n     +    be properly propagated back to the caller at that stage.\n     +\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       ## builtin/branch.c ##\n     @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n       \n       \t\t\tother = with_commit->item;\n       \t\t\twith_commit = with_commit->next;\n     --\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n     +-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n      -\t\t\t\treturn 1;\n     -+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit);\n     ++\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n      +\t\t\tif (ret)\n      +\t\t\t\treturn ret;\n       \t\t}\n     @@ commit-reach.c: int repo_in_merge_bases_many(struct repository *r, struct commit\n       \n       \tif (repo_parse_commit(r, commit))\n      -\t\treturn ret;\n     -+\t\treturn -1;\n     ++\t\treturn ignore_missing_commits ? 0 : -1;\n       \tfor (i = 0; i < nr_reference; i++) {\n       \t\tif (repo_parse_commit(r, reference[i]))\n      -\t\t\treturn ret;\n     -+\t\t\treturn -1;\n     ++\t\t\treturn ignore_missing_commits ? 0 : -1;\n       \n       \t\tgeneration = commit_graph_generation(reference[i]);\n       \t\tif (generation > max_generation)\n     @@ http-push.c: static int verify_merge_base(struct object_id *head_oid, struct ref\n       \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n       \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n       \t\t\t\t\t\t     remote->name);\n     -+\tint i = repo_in_merge_bases(the_repository, branch, head);\n     ++\tint ret = repo_in_merge_bases(the_repository, branch, head);\n       \n      -\treturn repo_in_merge_bases(the_repository, branch, head);\n     -+\tif (i < 0)\n     ++\tif (ret < 0)\n      +\t\texit(128);\n     -+\treturn i;\n     ++\treturn ret;\n       }\n       \n       static int delete_remote_branch(const char *pattern, int force)\n     @@ merge-ort.c: static int find_first_merges(struct repository *repo,\n      -\t\tif (repo_in_merge_bases(repo, b, commit))\n      +\t\tint ret = repo_in_merge_bases(repo, b, commit);\n      +\n     -+\t\tif (ret < 0)\n     ++\t\tif (ret < 0) {\n     ++\t\t\tobject_array_clear(&merges);\n     ++\t\t\trelease_revisions(&revs);\n      +\t\t\treturn ret;\n     ++\t\t}\n      +\t\tif (ret > 0)\n       \t\t\tadd_object_array(o, NULL, &merges);\n       \t}\n     @@ merge-ort.c: static int find_first_merges(struct repository *repo,\n      -\t\t\t\tbreak;\n      +\t\t\tif (i != j) {\n      +\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n     -+\t\t\t\tif (ret < 0)\n     ++\t\t\t\tif (ret < 0) {\n     ++\t\t\t\t\tobject_array_clear(&merges);\n     ++\t\t\t\t\trelease_revisions(&revs);\n      +\t\t\t\t\treturn ret;\n     ++\t\t\t\t}\n      +\t\t\t\tif (ret > 0) {\n      +\t\t\t\t\tcontains_another = 1;\n      +\t\t\t\t\tbreak;\n     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      -\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n      -\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n      +\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n     -+\tif (ret2 < 1) {\n     ++\tif (ret2 < 0) {\n      +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n      +\t\t\t path, NULL, NULL, NULL,\n      +\t\t\t _(\"Failed to merge submodule %s \"\n     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      +\t\t\t path);\n      +\t\tgoto cleanup;\n      +\t}\n     -+\tif (!ret2)\n     ++\tif (ret2 > 0)\n      +\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n     -+\tif (ret2 < 1) {\n     ++\tif (ret2 < 0) {\n      +\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n      +\t\t\t path, NULL, NULL, NULL,\n      +\t\t\t _(\"Failed to merge submodule %s \"\n     @@ merge-recursive.c: static int find_first_merges(struct repository *repo,\n       \t\tstruct object *o = &(commit->object);\n      -\t\tif (repo_in_merge_bases(repo, b, commit))\n      +\t\tint ret = repo_in_merge_bases(repo, b, commit);\n     -+\t\tif (ret < 0)\n     ++\t\tif (ret < 0) {\n     ++\t\t\tobject_array_clear(&merges);\n     ++\t\t\trelease_revisions(&revs);\n      +\t\t\treturn ret;\n     ++\t\t}\n      +\t\tif (ret)\n       \t\t\tadd_object_array(o, NULL, &merges);\n       \t}\n     @@ merge-recursive.c: static int find_first_merges(struct repository *repo,\n      -\t\t\t\tbreak;\n      +\t\t\tif (i != j) {\n      +\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n     -+\t\t\t\tif (ret < 0)\n     ++\t\t\t\tif (ret < 0) {\n     ++\t\t\t\t\tobject_array_clear(&merges);\n     ++\t\t\t\t\trelease_revisions(&revs);\n      +\t\t\t\t\treturn ret;\n     ++\t\t\t\t}\n      +\t\t\t\tif (ret > 0) {\n      +\t\t\t\t\tcontains_another = 1;\n      +\t\t\t\t\tbreak;\n     @@ merge-recursive.c: static int merge_submodule(struct merge_options *opt,\n      +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n      +\t\tgoto cleanup;\n      +\t}\n     -+\tif (ret2)\n     ++\tif (ret2 > 0)\n      +\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n      +\tif (ret2 < 0) {\n      +\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n     @@ shallow.c: static void post_assign_shallow(struct shallow_info *info,\n       \t\tfor (j = 0; j < bitmap_nr; j++)\n      -\t\t\tif (bitmap[0][j] &&\n      -\t\t\t    /* Step 7, reachability test at commit level */\n     --\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n     +-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n      -\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n      -\t\t\t\tdst++;\n      -\t\t\t\tbreak;\n      +\t\t\tif (bitmap[0][j]) {\n      +\t\t\t\t/* Step 7, reachability test at commit level */\n     -+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits);\n     ++\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n      +\t\t\t\tif (ret < 0)\n      +\t\t\t\t\texit(128);\n      +\t\t\t\tif (!ret) {\n     @@ shallow.c: static void post_assign_shallow(struct shallow_info *info,\n       \t}\n       \tinfo->nr_ours = dst;\n      @@ shallow.c: int delayed_reachability_test(struct shallow_info *si, int c)\n     - \t\t\t\t\t\t\t    commit,\n       \t\t\t\t\t\t\t    si->nr_commits,\n     - \t\t\t\t\t\t\t    si->commits);\n     + \t\t\t\t\t\t\t    si->commits,\n     + \t\t\t\t\t\t\t    1);\n      +\t\tif (si->reachable[c] < 0)\n      +\t\t\texit(128);\n       \t\tsi->need_reachability_test[c] = 0;\n  4:  7477b18adeb !  4:  53bdeddb4cb Prepare `paint_down_to_common()` for handling shallow commits\n     @@ Commit message\n          able to handle this situation gracefully.\n      \n          Currently, that logic pretends that a missing parent commit is\n     -    equivalent to a a missing parent commit, and for the purpose of\n     +    equivalent to a missing parent commit, and for the purpose of\n          `--update-shallow` that is exactly what we need it to do.\n      \n          Therefore, let's introduce a flag to indicate when that is precisely the\n     @@ Commit message\n          shallow root with receive.updateshallow on\") and t5538.4 (\"add new\n          shallow root with receive.updateshallow on\").\n      \n     +    Note: shallow commits' parents are set to `NULL` internally already,\n     +    therefore there is no need to special-case shallow repositories here, as\n     +    the merge-base logic will not try to access parent commits of shallow\n     +    commits.\n     +\n     +    Likewise, partial clones aren't an issue either: If a commit is missing\n     +    during the revision walk in the merge-base logic, it is fetched via\n     +    `promisor_remote_get_direct()`. And not only the single missing commit\n     +    object: Due to the way the \"promised\" objects are fetched (in\n     +    `fetch_objects()` in `promisor-remote.c`, using `fetch\n     +    --filter=blob:none`), there is no actual way to fetch a single commit\n     +    object, as the remote side will pass that commit OID to `pack-objects\n     +    --revs [...]` which in turn passes it to `rev-list` which interprets\n     +    this as a commit _range_ instead of a single object. Therefore, in\n     +    partial clones (unless they are shallow in addition), all commits\n     +    reachable from a commit that is in the local object database are also\n     +    present in that local database.\n     +\n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n       ## commit-reach.c ##\n     @@ commit-reach.c: static int queue_has_nonstale(struct prio_queue *queue)\n       \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n       \tstruct commit_list *result = NULL;\n      @@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repository *r,\n     - \t\t\tif ((p->object.flags & flags) == flags)\n     - \t\t\t\tcontinue;\n       \t\t\tif (repo_parse_commit(r, p)) {\n     + \t\t\t\tclear_prio_queue(&queue);\n     + \t\t\t\tfree_commit_list(result);\n      +\t\t\t\t/*\n      +\t\t\t\t * At this stage, we know that the commit is\n      +\t\t\t\t * missing: `repo_parse_commit()` uses\n     @@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repositor\n      +\t\t\t\t * corrupt commits would already have been\n      +\t\t\t\t * dispatched with a `die()`.\n      +\t\t\t\t */\n     - \t\t\t\tfree_commit_list(result);\n       \t\t\t\treturn NULL;\n       \t\t\t}\n     + \t\t\tp->object.flags |= flags;\n      @@ commit-reach.c: static struct commit_list *merge_bases_many(struct repository *r,\n       \t\t\treturn NULL;\n       \t}\n  5:  f8aedb89f1c !  5:  ec3ebf0ed17 commit-reach: start reporting errors in `paint_down_to_common()`\n     @@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repositor\n       \t\t\t}\n       \t\t\t/* Mark parents of a found merge stale */\n       \t\t\tflags |= STALE;\n     +@@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repository *r,\n     + \t\t\t\tcontinue;\n     + \t\t\tif (repo_parse_commit(r, p)) {\n     + \t\t\t\tclear_prio_queue(&queue);\n     +-\t\t\t\tfree_commit_list(result);\n     ++\t\t\t\tfree_commit_list(*result);\n     ++\t\t\t\t*result = NULL;\n     + \t\t\t\t/*\n     + \t\t\t\t * At this stage, we know that the commit is\n     + \t\t\t\t * missing: `repo_parse_commit()` uses\n      @@ commit-reach.c: static struct commit_list *paint_down_to_common(struct repository *r,\n       \t\t\t\t * corrupt commits would already have been\n       \t\t\t\t * dispatched with a `die()`.\n       \t\t\t\t */\n     --\t\t\t\tfree_commit_list(result);\n      -\t\t\t\treturn NULL;\n     -+\t\t\t\tfree_commit_list(*result);\n      +\t\t\t\tif (ignore_missing_commits)\n      +\t\t\t\t\treturn 0;\n      +\t\t\t\treturn error(_(\"could not parse commit %s\"),\n     @@ commit-reach.c: static struct commit_list *merge_bases_many(struct repository *r\n       \t}\n       \n      -\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n     -+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0)\n     ++\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n     ++\t\tfree_commit_list(list);\n      +\t\treturn NULL;\n     ++\t}\n       \n       \twhile (list) {\n       \t\tstruct commit *commit = pop_commit(&list);\n     @@ commit-reach.c: static int remove_redundant_no_gen(struct repository *r,\n      -\t\tcommon = paint_down_to_common(r, array[i], filled,\n      -\t\t\t\t\t      work, min_generation, 0);\n      +\t\tif (paint_down_to_common(r, array[i], filled,\n     -+\t\t\t\t\t work, min_generation, 0, &common))\n     ++\t\t\t\t\t work, min_generation, 0, &common)) {\n     ++\t\t\tclear_commit_marks(array[i], all_flags);\n     ++\t\t\tclear_commit_marks_many(filled, work, all_flags);\n     ++\t\t\tfree_commit_list(common);\n     ++\t\t\tfree(work);\n     ++\t\t\tfree(redundant);\n     ++\t\t\tfree(filled_index);\n      +\t\t\treturn -1;\n     ++\t\t}\n       \t\tif (array[i]->object.flags & PARENT2)\n       \t\t\tredundant[i] = 1;\n       \t\tfor (j = 0; j < filled; j++)\n  6:  0aca08a2cb5 !  6:  05756fbf71a merge_bases_many(): pass on errors from `paint_down_to_common()`\n     @@ commit-reach.c: static int paint_down_to_common(struct repository *r,\n      +\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n       \t}\n       \n     - \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0)\n     + \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n     + \t\tfree_commit_list(list);\n      -\t\treturn NULL;\n      +\t\treturn -1;\n     + \t}\n       \n       \twhile (list) {\n       \t\tstruct commit *commit = pop_commit(&list);\n  7:  03734cc09da =  7:  e3d37a326e5 get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n  8:  43c33e2eac4 !  8:  9ca504525b9 repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n     @@ object-name.c: int repo_get_oid_mb(struct repository *r,\n       \tif (!two)\n       \t\treturn -1;\n      -\tmbs = repo_get_merge_bases(r, one, two);\n     -+\tif (repo_get_merge_bases(r, one, two, &mbs) < 0)\n     ++\tif (repo_get_merge_bases(r, one, two, &mbs) < 0) {\n     ++\t\tfree_commit_list(mbs);\n      +\t\treturn -1;\n     ++\t}\n       \tif (!mbs || mbs->next)\n       \t\tst = -1;\n       \telse {\n     @@ revision.c: static int handle_dotdot_1(const char *arg, char *dotdot,\n       \t\t\treturn dotdot_missing(arg, dotdot, revs, symmetric);\n       \n      -\t\texclude = repo_get_merge_bases(the_repository, a, b);\n     -+\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n     ++\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) {\n     ++\t\t\tfree_commit_list(exclude);\n      +\t\t\treturn -1;\n     ++\t\t}\n       \t\tadd_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,\n       \t\t\t\t     flags_exclude);\n       \t\tadd_pending_commit_list(revs, exclude, flags_exclude);\n  9:  7fbf660e371 !  9:  b11879edb73 get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n     @@ builtin/merge-base.c: static int handle_independent(int count, const char **args\n       \t\tcommit_list_insert(get_commit_reference(args[i]), &revs);\n       \n      -\tresult = get_octopus_merge_bases(revs);\n     -+\tif (get_octopus_merge_bases(revs, &result) < 0)\n     ++\tif (get_octopus_merge_bases(revs, &result) < 0) {\n     ++\t\tfree_commit_list(revs);\n     ++\t\tfree_commit_list(result);\n      +\t\treturn 128;\n     ++\t}\n       \tfree_commit_list(revs);\n       \treduce_heads_replace(&result);\n       \n 10:  55757c3a35d = 10:  602a7383f72 repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n 11:  e7fcc96196c ! 11:  96850ed2d69 repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`\n     @@ builtin/merge-base.c\n      -\tresult = repo_get_merge_bases_many_dirty(the_repository, rev[0],\n      -\t\t\t\t\t\t rev_nr - 1, rev + 1);\n      +\tif (repo_get_merge_bases_many_dirty(the_repository, rev[0],\n     -+\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0)\n     ++\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0) {\n     ++\t\tfree_commit_list(result);\n      +\t\treturn -1;\n     ++\t}\n       \n       \tif (!result)\n       \t\treturn 1;\n 12:  33894600ae7 <  -:  ----------- paint_down_to_common(): special-case shallow/partial clones\n\n-- \ngitgitgadget\n"},{"id":"489136","messageId":"5c16becfb9b4741a1c91f1c560ad270b74048baa.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 02/11] Prepare `repo_in_merge_bases_many()` to optionally expect missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:41Z","receivedAt":"2024-02-22T13:21:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nCurrently this function treats unrelated commit histories the same way\nas commit histories with missing commit objects.\n\nTypically, missing commit objects constitute a corrupt repository,\nthough, and should be reported as such. The next commits will make it\nso, but there is one exception: In `git fetch --update-shallow` we\n_expect_ commit objects to be missing, and we do want to treat the\nnow-incomplete commit histories as unrelated.\n\nTo allow for that, let's introduce an additional parameter that is\npassed to `repo_in_merge_bases_many()` to trigger this behavior, and use\nit in the two callers in `shallow.c`.\n\nThis does not change behavior in this commit, but prepares in an\neasy-to-review way for the upcoming changes that will make the merge\nbase logic more stringent with regards to missing commit objects.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c        | 5 +++--\n commit-reach.h        | 3 ++-\n remote.c              | 2 +-\n shallow.c             | 5 +++--\n t/helper/test-reach.c | 2 +-\n 5 files changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7ea916f9ebd..ba2df8b8f3a 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -467,7 +467,7 @@ int repo_is_descendant_of(struct repository *r,\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n+\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n \t\t\t\treturn 1;\n \t\t}\n \t\treturn 0;\n@@ -478,7 +478,8 @@ int repo_is_descendant_of(struct repository *r,\n  * Is \"commit\" an ancestor of one of the \"references\"?\n  */\n int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference)\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits)\n {\n \tstruct commit_list *bases;\n \tint ret = 0, i;\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 35c4da49481..68f81549a44 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -30,7 +30,8 @@ int repo_in_merge_bases(struct repository *r,\n \t\t\tstruct commit *reference);\n int repo_in_merge_bases_many(struct repository *r,\n \t\t\t     struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference);\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits);\n \n /*\n  * Takes a list of commits and returns a new list where those\ndiff --git a/remote.c b/remote.c\nindex abb24822beb..763c80f4a7d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2675,7 +2675,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n \t\tif (MERGE_BASES_BATCH_SIZE < size)\n \t\t\tsize = MERGE_BASES_BATCH_SIZE;\n \n-\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))\n+\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk, 0)))\n \t\t\tbreak;\n \t}\n \ndiff --git a/shallow.c b/shallow.c\nindex ac728cdd778..dfcc1f86a7f 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -797,7 +797,7 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tfor (j = 0; j < bitmap_nr; j++)\n \t\t\tif (bitmap[0][j] &&\n \t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n+\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n \t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n \t\t\t\tdst++;\n \t\t\t\tbreak;\n@@ -828,7 +828,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\tsi->reachable[c] = repo_in_merge_bases_many(the_repository,\n \t\t\t\t\t\t\t    commit,\n \t\t\t\t\t\t\t    si->nr_commits,\n-\t\t\t\t\t\t\t    si->commits);\n+\t\t\t\t\t\t\t    si->commits,\n+\t\t\t\t\t\t\t    1);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 3e173399a00..aa816e168ea 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -113,7 +113,7 @@ int cmd__reach(int ac, const char **av)\n \t\t       repo_in_merge_bases(the_repository, A, B));\n \telse if (!strcmp(av[1], \"in_merge_bases_many\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1],\n-\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array));\n+\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-- \ngitgitgadget\n\n"},{"id":"489138","messageId":"4dd214f91d4783f29b03908cc0034156253889a7.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:42Z","receivedAt":"2024-02-22T13:21:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSome functions in Git's source code follow the convention that returning\na negative value indicates a fatal error, e.g. repository corruption.\n\nLet's use this convention in `repo_in_merge_bases()` to report when one\nof the specified commits is missing (i.e. when `repo_parse_commit()`\nreports an error).\n\nAlso adjust the callers of `repo_in_merge_bases()` to handle such\nnegative return values.\n\nNote: As of this patch, errors are returned only if any of the specified\nmerge heads is missing. Over the course of the next patches, missing\ncommits will also be reported by the `paint_down_to_common()` function,\nwhich is called by `repo_in_merge_bases_many()`, and those errors will\nbe properly propagated back to the caller at that stage.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch.c       | 12 +++++--\n builtin/fast-import.c  |  6 +++-\n builtin/fetch.c        |  2 ++\n builtin/log.c          |  7 ++--\n builtin/merge-base.c   |  6 +++-\n builtin/pull.c         |  4 +++\n builtin/receive-pack.c |  6 +++-\n commit-reach.c         | 12 ++++---\n http-push.c            |  5 ++-\n merge-ort.c            | 81 ++++++++++++++++++++++++++++++++++++------\n merge-recursive.c      | 54 +++++++++++++++++++++++-----\n shallow.c              | 18 ++++++----\n 12 files changed, 175 insertions(+), 38 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e7ee9bd0f15..7f9e79237f3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -161,6 +161,8 @@ static int branch_merged(int kind, const char *name,\n \n \tmerged = reference_rev ? repo_in_merge_bases(the_repository, rev,\n \t\t\t\t\t\t     reference_rev) : 0;\n+\tif (merged < 0)\n+\t\texit(128);\n \n \t/*\n \t * After the safety valve is fully redefined to \"check with\n@@ -169,9 +171,13 @@ static int branch_merged(int kind, const char *name,\n \t * any of the following code, but during the transition period,\n \t * a gentle reminder is in order.\n \t */\n-\tif ((head_rev != reference_rev) &&\n-\t    (head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != merged) {\n-\t\tif (merged)\n+\tif (head_rev != reference_rev) {\n+\t\tint expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0;\n+\t\tif (expect < 0)\n+\t\t\texit(128);\n+\t\tif (expect == merged)\n+\t\t\t; /* okay */\n+\t\telse if (merged)\n \t\t\twarning(_(\"deleting branch '%s' that has been merged to\\n\"\n \t\t\t\t\"         '%s', but not yet merged to HEAD\"),\n \t\t\t\tname, reference_name);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 444f41cf8ca..14c2efa88fc 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1625,6 +1625,7 @@ static int update_branch(struct branch *b)\n \t\toidclr(&old_oid);\n \tif (!force_update && !is_null_oid(&old_oid)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n+\t\tint ret;\n \n \t\told_cmit = lookup_commit_reference_gently(the_repository,\n \t\t\t\t\t\t\t  &old_oid, 0);\n@@ -1633,7 +1634,10 @@ static int update_branch(struct branch *b)\n \t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n \n-\t\tif (!repo_in_merge_bases(the_repository, old_cmit, new_cmit)) {\n+\t\tret = repo_in_merge_bases(the_repository, old_cmit, new_cmit);\n+\t\tif (ret < 0)\n+\t\t\texit(128);\n+\t\tif (!ret) {\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, oid_to_hex(&b->oid),\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex fd134ba74d9..0584a1f8b64 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -978,6 +978,8 @@ static int update_local_ref(struct ref *ref,\n \t\tuint64_t t_before = getnanotime();\n \t\tfast_forward = repo_in_merge_bases(the_repository, current,\n \t\t\t\t\t\t   updated);\n+\t\tif (fast_forward < 0)\n+\t\t\texit(128);\n \t\tforced_updates_ms += (getnanotime() - t_before) / 1000000;\n \t} else {\n \t\tfast_forward = 1;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ba775d7b5cf..1705da71aca 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1623,7 +1623,7 @@ static struct commit *get_base_commit(const char *base_commit,\n {\n \tstruct commit *base = NULL;\n \tstruct commit **rev;\n-\tint i = 0, rev_nr = 0, auto_select, die_on_failure;\n+\tint i = 0, rev_nr = 0, auto_select, die_on_failure, ret;\n \n \tswitch (auto_base) {\n \tcase AUTO_BASE_NEVER:\n@@ -1723,7 +1723,10 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\trev_nr = DIV_ROUND_UP(rev_nr, 2);\n \t}\n \n-\tif (!repo_in_merge_bases(the_repository, base, rev[0])) {\n+\tret = repo_in_merge_bases(the_repository, base, rev[0]);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (!ret) {\n \t\tif (die_on_failure) {\n \t\t\tdie(_(\"base commit should be the ancestor of revision list\"));\n \t\t} else {\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex e68b7fe45d7..0308fd73289 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -103,12 +103,16 @@ static int handle_octopus(int count, const char **args, int show_all)\n static int handle_is_ancestor(int argc, const char **argv)\n {\n \tstruct commit *one, *two;\n+\tint ret;\n \n \tif (argc != 2)\n \t\tdie(\"--is-ancestor takes exactly two commits\");\n \tone = get_commit_reference(argv[0]);\n \ttwo = get_commit_reference(argv[1]);\n-\tif (repo_in_merge_bases(the_repository, one, two))\n+\tret = repo_in_merge_bases(the_repository, one, two);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (ret)\n \t\treturn 0;\n \telse\n \t\treturn 1;\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex be2b2c9ebc9..e6f2942c0c5 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -931,6 +931,8 @@ static int get_can_ff(struct object_id *orig_head,\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n \tret = repo_is_descendant_of(the_repository, merge_head, list);\n \tfree_commit_list(list);\n+\tif (ret < 0)\n+\t\texit(128);\n \treturn ret;\n }\n \n@@ -955,6 +957,8 @@ static int already_up_to_date(struct object_id *orig_head,\n \t\tcommit_list_insert(theirs, &list);\n \t\tok = repo_is_descendant_of(the_repository, ours, list);\n \t\tfree_commit_list(list);\n+\t\tif (ok < 0)\n+\t\t\texit(128);\n \t\tif (!ok)\n \t\t\treturn 0;\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8c4f0cb90a9..956fea6293e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1546,6 +1546,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t    starts_with(name, \"refs/heads/\")) {\n \t\tstruct object *old_object, *new_object;\n \t\tstruct commit *old_commit, *new_commit;\n+\t\tint ret2;\n \n \t\told_object = parse_object(the_repository, old_oid);\n \t\tnew_object = parse_object(the_repository, new_oid);\n@@ -1559,7 +1560,10 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t}\n \t\told_commit = (struct commit *)old_object;\n \t\tnew_commit = (struct commit *)new_object;\n-\t\tif (!repo_in_merge_bases(the_repository, old_commit, new_commit)) {\n+\t\tret2 = repo_in_merge_bases(the_repository, old_commit, new_commit);\n+\t\tif (ret2 < 0)\n+\t\t\texit(128);\n+\t\tif (!ret2) {\n \t\t\trp_error(\"denying non-fast-forward %s\"\n \t\t\t\t \" (you should pull first)\", name);\n \t\t\tret = \"non-fast-forward\";\ndiff --git a/commit-reach.c b/commit-reach.c\nindex ba2df8b8f3a..5ff71d72d51 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -464,11 +464,13 @@ int repo_is_descendant_of(struct repository *r,\n \t} else {\n \t\twhile (with_commit) {\n \t\t\tstruct commit *other;\n+\t\t\tint ret;\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n-\t\t\t\treturn 1;\n+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n \t\t}\n \t\treturn 0;\n \t}\n@@ -486,10 +488,10 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n \tif (repo_parse_commit(r, commit))\n-\t\treturn ret;\n+\t\treturn ignore_missing_commits ? 0 : -1;\n \tfor (i = 0; i < nr_reference; i++) {\n \t\tif (repo_parse_commit(r, reference[i]))\n-\t\t\treturn ret;\n+\t\t\treturn ignore_missing_commits ? 0 : -1;\n \n \t\tgeneration = commit_graph_generation(reference[i]);\n \t\tif (generation > max_generation)\n@@ -598,6 +600,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)\n \tcommit_list_insert(old_commit, &old_commit_list);\n \tret = repo_is_descendant_of(the_repository,\n \t\t\t\t    new_commit, old_commit_list);\n+\tif (ret < 0)\n+\t\texit(128);\n \tfree_commit_list(old_commit_list);\n \treturn ret;\n }\ndiff --git a/http-push.c b/http-push.c\nindex a704f490fdb..24c16a4f5ff 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1576,8 +1576,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote)\n \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n \t\t\t\t\t\t     remote->name);\n+\tint ret = repo_in_merge_bases(the_repository, branch, head);\n \n-\treturn repo_in_merge_bases(the_repository, branch, head);\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret;\n }\n \n static int delete_remote_branch(const char *pattern, int force)\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 6491070d965..9f3af46333a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -544,6 +544,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n \tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n+\tCONFLICT_SUBMODULE_CORRUPT,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -596,7 +597,9 @@ static const char *type_short_descriptions[] = {\n \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n \t\t\"CONFLICT (submodule may have rewinds)\",\n \t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n-\t\t\"CONFLICT (submodule lacks merge base)\"\n+\t\t\"CONFLICT (submodule lacks merge base)\",\n+\t[CONFLICT_SUBMODULE_CORRUPT] =\n+\t\t\"CONFLICT (submodule corrupt)\"\n };\n \n struct logical_conflict_info {\n@@ -1710,7 +1713,14 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret > 0)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1725,9 +1735,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1749,7 +1767,7 @@ static int merge_submodule(struct merge_options *opt,\n {\n \tstruct repository subrepo;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1796,8 +1814,26 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s \"\n@@ -1807,7 +1843,16 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, b);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1816,7 +1861,16 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, a);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1841,6 +1895,13 @@ static int merge_submodule(struct merge_options *opt,\n \tparent_count = find_first_merges(&subrepo, path, commit_a, commit_b,\n \t\t\t\t\t &merges);\n \tswitch (parent_count) {\n+\tcase -1:\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tbreak;\n \tcase 0:\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n \t\t\t path, NULL, NULL, NULL,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3beb0801b1..0d931cc14ad 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1144,7 +1144,13 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1159,9 +1165,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1197,7 +1211,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   const struct object_id *b)\n {\n \tstruct repository subrepo;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_base, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1234,14 +1248,29 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (commits don't follow merge-base)\"), path);\n \t\tgoto cleanup;\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, b);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1254,7 +1283,12 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, a);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&o->oid,\n \t\t\t\t\t\t\t&a->oid,\n \t\t\t\t\t\t\t&b->oid);\n+\t\t\tif (result->clean < 0)\n+\t\t\t\treturn -1;\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n \t\t\tcase MERGE_VARIANT_NORMAL:\ndiff --git a/shallow.c b/shallow.c\nindex dfcc1f86a7f..f71496f35c3 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -795,12 +795,16 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tif (!*bitmap)\n \t\t\tcontinue;\n \t\tfor (j = 0; j < bitmap_nr; j++)\n-\t\t\tif (bitmap[0][j] &&\n-\t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n-\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n-\t\t\t\tdst++;\n-\t\t\t\tbreak;\n+\t\t\tif (bitmap[0][j]) {\n+\t\t\t\t/* Step 7, reachability test at commit level */\n+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\texit(128);\n+\t\t\t\tif (!ret) {\n+\t\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n+\t\t\t\t\tdst++;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t}\n \tinfo->nr_ours = dst;\n@@ -830,6 +834,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\t\t\t\t\t\t    si->nr_commits,\n \t\t\t\t\t\t\t    si->commits,\n \t\t\t\t\t\t\t    1);\n+\t\tif (si->reachable[c] < 0)\n+\t\t\texit(128);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\n-- \ngitgitgadget\n\n"},{"id":"489137","messageId":"53bdeddb4cb0293b2d7f896b5a8943ab10b57037.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:43Z","receivedAt":"2024-02-22T13:21:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen `git fetch --update-shallow` needs to test for commit ancestry, it\ncan naturally run into a missing object (e.g. if it is a parent of a\nshallow commit). To accommodate, the merge base logic will need to be\nable to handle this situation gracefully.\n\nCurrently, that logic pretends that a missing parent commit is\nequivalent to a missing parent commit, and for the purpose of\n`--update-shallow` that is exactly what we need it to do.\n\nTherefore, let's introduce a flag to indicate when that is precisely the\nlogic we want.\n\nWe need a flag, and cannot rely on `is_repository_shallow()` to indicate\nthat situation, because that function would return 0: There may not\nactually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\nshallow root with receive.updateshallow on\") and t5538.4 (\"add new\nshallow root with receive.updateshallow on\").\n\nNote: shallow commits' parents are set to `NULL` internally already,\ntherefore there is no need to special-case shallow repositories here, as\nthe merge-base logic will not try to access parent commits of shallow\ncommits.\n\nLikewise, partial clones aren't an issue either: If a commit is missing\nduring the revision walk in the merge-base logic, it is fetched via\n`promisor_remote_get_direct()`. And not only the single missing commit\nobject: Due to the way the \"promised\" objects are fetched (in\n`fetch_objects()` in `promisor-remote.c`, using `fetch\n--filter=blob:none`), there is no actual way to fetch a single commit\nobject, as the remote side will pass that commit OID to `pack-objects\n--revs [...]` which in turn passes it to `rev-list` which interprets\nthis as a commit _range_ instead of a single object. Therefore, in\npartial clones (unless they are shallow in addition), all commits\nreachable from a commit that is in the local object database are also\npresent in that local database.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5ff71d72d51..7112b10eeea 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -53,7 +53,8 @@ static int queue_has_nonstale(struct prio_queue *queue)\n static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t\t\tstruct commit *one, int n,\n \t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation)\n+\t\t\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\t\t\tint ignore_missing_commits)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n \tstruct commit_list *result = NULL;\n@@ -108,6 +109,13 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n \t\t\t\tfree_commit_list(result);\n+\t\t\t\t/*\n+\t\t\t\t * At this stage, we know that the commit is\n+\t\t\t\t * missing: `repo_parse_commit()` uses\n+\t\t\t\t * `OBJECT_INFO_DIE_IF_CORRUPT` and therefore\n+\t\t\t\t * corrupt commits would already have been\n+\t\t\t\t * dispatched with a `die()`.\n+\t\t\t\t */\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n@@ -143,7 +151,7 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0);\n+\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -214,7 +222,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n \t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation);\n+\t\t\t\t\t      work, min_generation, 0);\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -504,7 +512,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \n \tbases = paint_down_to_common(r, commit,\n \t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation);\n+\t\t\t\t     generation, ignore_missing_commits);\n \tif (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n-- \ngitgitgadget\n\n"},{"id":"489139","messageId":"ec3ebf0ed17fa38c51f0e6baeff79999c0a3f070.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 05/11] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:44Z","receivedAt":"2024-02-22T13:22:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIf a commit cannot be parsed, it is currently ignored when looking for\nmerge bases. That's undesirable as the operation can pretend success in\na corrupt repository, even though the command should fail with an error\nmessage.\n\nLet's start at the bottom of the stack by teaching the\n`paint_down_to_common()` function to return an `int`: if negative, it\nindicates fatal error, if 0 success.\n\nThis requires a couple of callers to be adjusted accordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 66 ++++++++++++++++++++++++++++++++++----------------\n 1 file changed, 45 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7112b10eeea..9148a7dcbc0 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -50,14 +50,14 @@ static int queue_has_nonstale(struct prio_queue *queue)\n }\n \n /* all input commits in one and twos[] must have been parsed! */\n-static struct commit_list *paint_down_to_common(struct repository *r,\n-\t\t\t\t\t\tstruct commit *one, int n,\n-\t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation,\n-\t\t\t\t\t\tint ignore_missing_commits)\n+static int paint_down_to_common(struct repository *r,\n+\t\t\t\tstruct commit *one, int n,\n+\t\t\t\tstruct commit **twos,\n+\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\tint ignore_missing_commits,\n+\t\t\t\tstruct commit_list **result)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n-\tstruct commit_list *result = NULL;\n \tint i;\n \ttimestamp_t last_gen = GENERATION_NUMBER_INFINITY;\n \n@@ -66,8 +66,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \n \tone->object.flags |= PARENT1;\n \tif (!n) {\n-\t\tcommit_list_append(one, &result);\n-\t\treturn result;\n+\t\tcommit_list_append(one, result);\n+\t\treturn 0;\n \t}\n \tprio_queue_put(&queue, one);\n \n@@ -95,7 +95,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\tif (flags == (PARENT1 | PARENT2)) {\n \t\t\tif (!(commit->object.flags & RESULT)) {\n \t\t\t\tcommit->object.flags |= RESULT;\n-\t\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\t\tcommit_list_insert_by_date(commit, result);\n \t\t\t}\n \t\t\t/* Mark parents of a found merge stale */\n \t\t\tflags |= STALE;\n@@ -108,7 +108,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\tcontinue;\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n-\t\t\t\tfree_commit_list(result);\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n \t\t\t\t/*\n \t\t\t\t * At this stage, we know that the commit is\n \t\t\t\t * missing: `repo_parse_commit()` uses\n@@ -116,7 +117,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t * corrupt commits would already have been\n \t\t\t\t * dispatched with a `die()`.\n \t\t\t\t */\n-\t\t\t\treturn NULL;\n+\t\t\t\tif (ignore_missing_commits)\n+\t\t\t\t\treturn 0;\n+\t\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t\t     oid_to_hex(&p->object.oid));\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n@@ -124,7 +128,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t}\n \n \tclear_prio_queue(&queue);\n-\treturn result;\n+\treturn 0;\n }\n \n static struct commit_list *merge_bases_many(struct repository *r,\n@@ -151,7 +155,10 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n+\t\tfree_commit_list(list);\n+\t\treturn NULL;\n+\t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -205,7 +212,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \tfor (i = 0; i < cnt; i++)\n \t\trepo_parse_commit(r, array[i]);\n \tfor (i = 0; i < cnt; i++) {\n-\t\tstruct commit_list *common;\n+\t\tstruct commit_list *common = NULL;\n \t\ttimestamp_t min_generation = commit_graph_generation(array[i]);\n \n \t\tif (redundant[i])\n@@ -221,8 +228,16 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\tif (curr_generation < min_generation)\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n-\t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation, 0);\n+\t\tif (paint_down_to_common(r, array[i], filled,\n+\t\t\t\t\t work, min_generation, 0, &common)) {\n+\t\t\tclear_commit_marks(array[i], all_flags);\n+\t\t\tclear_commit_marks_many(filled, work, all_flags);\n+\t\t\tfree_commit_list(common);\n+\t\t\tfree(work);\n+\t\t\tfree(redundant);\n+\t\t\tfree(filled_index);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -422,6 +437,10 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tclear_commit_marks_many(n, twos, all_flags);\n \n \tcnt = remove_redundant(r, rslt, cnt);\n+\tif (cnt < 0) {\n+\t\tfree(rslt);\n+\t\treturn NULL;\n+\t}\n \tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n \t\tcommit_list_insert_by_date(rslt[i], &result);\n@@ -491,7 +510,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \t\t\t     int nr_reference, struct commit **reference,\n \t\t\t     int ignore_missing_commits)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n@@ -510,10 +529,11 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \tif (generation > max_generation)\n \t\treturn ret;\n \n-\tbases = paint_down_to_common(r, commit,\n-\t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation, ignore_missing_commits);\n-\tif (commit->object.flags & PARENT2)\n+\tif (paint_down_to_common(r, commit,\n+\t\t\t\t nr_reference, reference,\n+\t\t\t\t generation, ignore_missing_commits, &bases))\n+\t\tret = -1;\n+\telse if (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n \tclear_commit_marks_many(nr_reference, reference, all_flags);\n@@ -566,6 +586,10 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \t\t}\n \t}\n \tnum_head = remove_redundant(the_repository, array, num_head);\n+\tif (num_head < 0) {\n+\t\tfree(array);\n+\t\treturn NULL;\n+\t}\n \tfor (i = 0; i < num_head; i++)\n \t\ttail = &commit_list_insert(array[i], tail)->next;\n \tfree(array);\n-- \ngitgitgadget\n\n"},{"id":"489140","messageId":"05756fbf71a2ff4932fb2c140659d07a87db25ee.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 06/11] merge_bases_many(): pass on errors from `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:45Z","receivedAt":"2024-02-22T13:22:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `paint_down_to_common()` function was just taught to indicate\nparsing errors, and now the `merge_bases_many()` function is aware of\nthat, too.\n\nOne tricky aspect is that `merge_bases_many()` parses commits of its\nown, but wants to gracefully handle the scenario where NULL is passed as\na merge head, returning the empty list of merge bases. The way this was\nhandled involved calling `repo_parse_commit(NULL)` and relying on it to\nreturn an error. This has to be done differently now so that we can\nhandle missing commits correctly by producing a fatal error.\n\nNext step: adjust the caller of `merge_bases_many()`:\n`get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 9148a7dcbc0..2c74583c8e0 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -131,41 +131,49 @@ static int paint_down_to_common(struct repository *r,\n \treturn 0;\n }\n \n-static struct commit_list *merge_bases_many(struct repository *r,\n-\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t    struct commit **twos)\n+static int merge_bases_many(struct repository *r,\n+\t\t\t    struct commit *one, int n,\n+\t\t\t    struct commit **twos,\n+\t\t\t    struct commit_list **result)\n {\n \tstruct commit_list *list = NULL;\n-\tstruct commit_list *result = NULL;\n \tint i;\n \n \tfor (i = 0; i < n; i++) {\n-\t\tif (one == twos[i])\n+\t\tif (one == twos[i]) {\n \t\t\t/*\n \t\t\t * We do not mark this even with RESULT so we do not\n \t\t\t * have to clean it up.\n \t\t\t */\n-\t\t\treturn commit_list_insert(one, &result);\n+\t\t\t*result = commit_list_insert(one, result);\n+\t\t\treturn 0;\n+\t\t}\n \t}\n \n+\tif (!one)\n+\t\treturn 0;\n \tif (repo_parse_commit(r, one))\n-\t\treturn NULL;\n+\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t     oid_to_hex(&one->object.oid));\n \tfor (i = 0; i < n; i++) {\n+\t\tif (!twos[i])\n+\t\t\treturn 0;\n \t\tif (repo_parse_commit(r, twos[i]))\n-\t\t\treturn NULL;\n+\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n \t}\n \n \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n \t\tfree_commit_list(list);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n \t\tif (!(commit->object.flags & STALE))\n-\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\tcommit_list_insert_by_date(commit, result);\n \t}\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n@@ -410,10 +418,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tresult = merge_bases_many(r, one, n, twos);\n+\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n+\t\treturn NULL;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n \t\t\treturn result;\n-- \ngitgitgadget\n\n"},{"id":"489141","messageId":"e3d37a326e577420bb03ed54fb19ba3639ae3836.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 07/11] get_merge_bases_many_0(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:46Z","receivedAt":"2024-02-22T13:22:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate\nparsing errors, and now the `get_merge_bases_many_0()` function is aware\nof that, too.\n\nNext step: adjust the callers of `get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 57 +++++++++++++++++++++++++++++++-------------------\n 1 file changed, 36 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 2c74583c8e0..5fa0abc7d1e 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -410,37 +410,38 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt\n \treturn remove_redundant_no_gen(r, array, cnt);\n }\n \n-static struct commit_list *get_merge_bases_many_0(struct repository *r,\n-\t\t\t\t\t\t  struct commit *one,\n-\t\t\t\t\t\t  int n,\n-\t\t\t\t\t\t  struct commit **twos,\n-\t\t\t\t\t\t  int cleanup)\n+static int get_merge_bases_many_0(struct repository *r,\n+\t\t\t\t  struct commit *one,\n+\t\t\t\t  int n,\n+\t\t\t\t  struct commit **twos,\n+\t\t\t\t  int cleanup,\n+\t\t\t\t  struct commit_list **result)\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n-\t\treturn NULL;\n+\tif (merge_bases_many(r, one, n, twos, result) < 0)\n+\t\treturn -1;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n-\t\t\treturn result;\n+\t\t\treturn 0;\n \t}\n-\tif (!result || !result->next) {\n+\tif (!*result || !(*result)->next) {\n \t\tif (cleanup) {\n \t\t\tclear_commit_marks(one, all_flags);\n \t\t\tclear_commit_marks_many(n, twos, all_flags);\n \t\t}\n-\t\treturn result;\n+\t\treturn 0;\n \t}\n \n \t/* There are more than one */\n-\tcnt = commit_list_count(result);\n+\tcnt = commit_list_count(*result);\n \tCALLOC_ARRAY(rslt, cnt);\n-\tfor (list = result, i = 0; list; list = list->next)\n+\tfor (list = *result, i = 0; list; list = list->next)\n \t\trslt[i++] = list->item;\n-\tfree_commit_list(result);\n+\tfree_commit_list(*result);\n+\t*result = NULL;\n \n \tclear_commit_marks(one, all_flags);\n \tclear_commit_marks_many(n, twos, all_flags);\n@@ -448,13 +449,12 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tcnt = remove_redundant(r, rslt, cnt);\n \tif (cnt < 0) {\n \t\tfree(rslt);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n-\tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n-\t\tcommit_list_insert_by_date(rslt[i], &result);\n+\t\tcommit_list_insert_by_date(rslt[i], result);\n \tfree(rslt);\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n@@ -462,7 +462,12 @@ struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      int n,\n \t\t\t\t\t      struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n@@ -470,14 +475,24 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    int n,\n \t\t\t\t\t\t    struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 0);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases(struct repository *r,\n \t\t\t\t\t struct commit *one,\n \t\t\t\t\t struct commit *two)\n {\n-\treturn get_merge_bases_many_0(r, one, 1, &two, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"489142","messageId":"9ca504525b96fdeb20adb41ef02626b8ed12bff8.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 08/11] repo_get_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:47Z","receivedAt":"2024-02-22T13:22:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `repo_get_merge_bases()` macro) is aware of that, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext step: adjust the callers of `get_octopus_merge_bases()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/log.c                    | 10 +++++-----\n builtin/merge-tree.c             |  5 +++--\n builtin/merge.c                  | 20 ++++++++++++--------\n builtin/rebase.c                 |  8 +++++---\n builtin/rev-parse.c              |  5 +++--\n commit-reach.c                   | 23 +++++++++++------------\n commit-reach.h                   |  7 ++++---\n diff-lib.c                       |  5 +++--\n log-tree.c                       |  5 +++--\n merge-ort.c                      |  6 +++++-\n merge-recursive.c                |  4 +++-\n notes-merge.c                    |  3 ++-\n object-name.c                    |  7 +++++--\n revision.c                       | 12 ++++++++----\n sequencer.c                      |  8 ++++++--\n submodule.c                      |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 12 ++++++++++++\n 17 files changed, 96 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 1705da71aca..befafd6ae04 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1702,11 +1702,11 @@ static struct commit *get_base_commit(const char *base_commit,\n \t */\n \twhile (rev_nr > 1) {\n \t\tfor (i = 0; i < rev_nr / 2; i++) {\n-\t\t\tstruct commit_list *merge_base;\n-\t\t\tmerge_base = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  rev[2 * i],\n-\t\t\t\t\t\t\t  rev[2 * i + 1]);\n-\t\t\tif (!merge_base || merge_base->next) {\n+\t\t\tstruct commit_list *merge_base = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository,\n+\t\t\t\t\t\t rev[2 * i],\n+\t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n+\t\t\t    !merge_base || merge_base->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex a35e0452d66..76200250629 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -463,8 +463,9 @@ static int real_merge(struct merge_tree_options *o,\n \t\t * Get the merge bases, in reverse order; see comment above\n \t\t * merge_incore_recursive in merge-ort.h\n \t\t */\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, parent1,\n-\t\t\t\t\t\t   parent2);\n+\t\tif (repo_get_merge_bases(the_repository, parent1,\n+\t\t\t\t\t parent2, &merge_bases) < 0)\n+\t\t\texit(128);\n \t\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d748d46e135..ac9d58adc29 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1517,10 +1517,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (!remoteheads)\n \t\t; /* already up-to-date */\n-\telse if (!remoteheads->next)\n-\t\tcommon = repo_get_merge_bases(the_repository, head_commit,\n-\t\t\t\t\t      remoteheads->item);\n-\telse {\n+\telse if (!remoteheads->next) {\n+\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t remoteheads->item, &common) < 0) {\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n+\t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n \t\tcommon = get_octopus_merge_bases(list);\n@@ -1631,7 +1634,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tstruct commit_list *j;\n \n \t\tfor (j = remoteheads; j; j = j->next) {\n-\t\t\tstruct commit_list *common_one;\n+\t\t\tstruct commit_list *common_one = NULL;\n \t\t\tstruct commit *common_item;\n \n \t\t\t/*\n@@ -1639,9 +1642,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t * merge_bases again, otherwise \"git merge HEAD^\n \t\t\t * HEAD^^\" would be missed.\n \t\t\t */\n-\t\t\tcommon_one = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  head_commit,\n-\t\t\t\t\t\t\t  j->item);\n+\t\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t\t j->item, &common_one) < 0)\n+\t\t\t\texit(128);\n+\n \t\t\tcommon_item = common_one->item;\n \t\t\tfree_commit_list(common_one);\n \t\t\tif (!oideq(&common_item->object.oid, &j->item->object.oid)) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 043c65dccd9..06a55fc7325 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -879,7 +879,8 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!upstream)\n \t\tgoto done;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, upstream, head);\n+\tif (repo_get_merge_bases(the_repository, upstream, head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\tgoto done;\n \n@@ -898,8 +899,9 @@ static void fill_branch_base(struct rebase_options *options,\n {\n \tstruct commit_list *merge_bases = NULL;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, options->onto,\n-\t\t\t\t\t   options->orig_head);\n+\tif (repo_get_merge_bases(the_repository, options->onto,\n+\t\t\t\t options->orig_head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\toidcpy(branch_base, null_oid());\n \telse\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex fde8861ca4e..c97d0f6144c 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -297,7 +297,7 @@ static int try_difference(const char *arg)\n \t\tshow_rev(NORMAL, &end_oid, end);\n \t\tshow_rev(symmetric ? NORMAL : REVERSED, &start_oid, start);\n \t\tif (symmetric) {\n-\t\t\tstruct commit_list *exclude;\n+\t\t\tstruct commit_list *exclude = NULL;\n \t\t\tstruct commit *a, *b;\n \t\t\ta = lookup_commit_reference(the_repository, &start_oid);\n \t\t\tb = lookup_commit_reference(the_repository, &end_oid);\n@@ -305,7 +305,8 @@ static int try_difference(const char *arg)\n \t\t\t\t*dotdot = '.';\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n+\t\t\t\texit(128);\n \t\t\twhile (exclude) {\n \t\t\t\tstruct commit *commit = pop_commit(&exclude);\n \t\t\t\tshow_rev(REVERSED, &commit->object.oid, NULL);\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5fa0abc7d1e..10e625ff51b 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -189,9 +189,12 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n \t\tfor (j = ret; j; j = j->next) {\n-\t\t\tstruct commit_list *bases;\n-\t\t\tbases = repo_get_merge_bases(the_repository, i->item,\n-\t\t\t\t\t\t     j->item);\n+\t\t\tstruct commit_list *bases = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository, i->item,\n+\t\t\t\t\t\t j->item, &bases) < 0) {\n+\t\t\t\tfree_commit_list(bases);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n \t\t\telse\n@@ -483,16 +486,12 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \treturn result;\n }\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *one,\n-\t\t\t\t\t struct commit *two)\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *one,\n+\t\t\t struct commit *two,\n+\t\t\t struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, 1, &two, 1, result);\n }\n \n /*\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 68f81549a44..2c6fcdd34f6 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -9,9 +9,10 @@ struct ref_filter;\n struct object_id;\n struct object_array;\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *rev1,\n-\t\t\t\t\t struct commit *rev2);\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *rev1,\n+\t\t\t struct commit *rev2,\n+\t\t\t struct commit_list **result);\n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      struct commit *one, int n,\n \t\t\t\t\t      struct commit **twos);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 0e9ec4f68af..498224ccce2 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -565,7 +565,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n {\n \tint i;\n \tstruct commit *mb_child[2] = {0};\n-\tstruct commit_list *merge_bases;\n+\tstruct commit_list *merge_bases = NULL;\n \n \tfor (i = 0; i < revs->pending.nr; i++) {\n \t\tstruct object *obj = revs->pending.objects[i].item;\n@@ -592,7 +592,8 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n \t\tmb_child[1] = lookup_commit_reference(the_repository, &oid);\n \t}\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]);\n+\tif (repo_get_merge_bases(the_repository, mb_child[0], mb_child[1], &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases)\n \t\tdie(_(\"no merge base found\"));\n \tif (merge_bases->next)\ndiff --git a/log-tree.c b/log-tree.c\nindex 504da6b519e..4f337766a39 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1010,7 +1010,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \t\t\t   struct object_id *oid)\n {\n \tstruct merge_options o;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct merge_result res = {0};\n \tstruct pretty_print_context ctx = {0};\n \tstruct commit *parent1 = parents->item;\n@@ -1035,7 +1035,8 @@ static int do_remerge_diff(struct rev_info *opt,\n \t/* Parse the relevant commits and get the merge bases */\n \tparse_commit_or_die(parent1);\n \tparse_commit_or_die(parent2);\n-\tbases = repo_get_merge_bases(the_repository, parent1, parent2);\n+\tif (repo_get_merge_bases(the_repository, parent1, parent2, &bases) < 0)\n+\t\texit(128);\n \n \t/* Re-merge the parents */\n \tmerge_incore_recursive(&o, bases, parent1, parent2, &res);\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9f3af46333a..90d8495ca1f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5066,7 +5066,11 @@ static void merge_ort_internal(struct merge_options *opt,\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tresult->clean = -1;\n+\t\t\treturn;\n+\t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0d931cc14ad..d609373d960 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3638,7 +3638,9 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0)\n+\t\t\treturn -1;\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 8799b522a55..51282934ae6 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -607,7 +607,8 @@ int notes_merge(struct notes_merge_options *o,\n \tassert(local && remote);\n \n \t/* Find merge bases */\n-\tbases = repo_get_merge_bases(the_repository, local, remote);\n+\tif (repo_get_merge_bases(the_repository, local, remote, &bases) < 0)\n+\t\texit(128);\n \tif (!bases) {\n \t\tbase_oid = null_oid();\n \t\tbase_tree_oid = the_hash_algo->empty_tree;\ndiff --git a/object-name.c b/object-name.c\nindex 0bfa29dbbfe..63bec6d9a2b 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1481,7 +1481,7 @@ int repo_get_oid_mb(struct repository *r,\n \t\t    struct object_id *oid)\n {\n \tstruct commit *one, *two;\n-\tstruct commit_list *mbs;\n+\tstruct commit_list *mbs = NULL;\n \tstruct object_id oid_tmp;\n \tconst char *dots;\n \tint st;\n@@ -1509,7 +1509,10 @@ int repo_get_oid_mb(struct repository *r,\n \ttwo = lookup_commit_reference_gently(r, &oid_tmp, 0);\n \tif (!two)\n \t\treturn -1;\n-\tmbs = repo_get_merge_bases(r, one, two);\n+\tif (repo_get_merge_bases(r, one, two, &mbs) < 0) {\n+\t\tfree_commit_list(mbs);\n+\t\treturn -1;\n+\t}\n \tif (!mbs || mbs->next)\n \t\tst = -1;\n \telse {\ndiff --git a/revision.c b/revision.c\nindex 00d5c29bfce..eb0d550842f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1965,7 +1965,7 @@ static void add_pending_commit_list(struct rev_info *revs,\n \n static void prepare_show_merge(struct rev_info *revs)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct commit *head, *other;\n \tstruct object_id oid;\n \tconst char **prune = NULL;\n@@ -1980,7 +1980,8 @@ static void prepare_show_merge(struct rev_info *revs)\n \tother = lookup_commit_or_die(&oid, \"MERGE_HEAD\");\n \tadd_pending_object(revs, &head->object, \"HEAD\");\n \tadd_pending_object(revs, &other->object, \"MERGE_HEAD\");\n-\tbases = repo_get_merge_bases(the_repository, head, other);\n+\tif (repo_get_merge_bases(the_repository, head, other, &bases) < 0)\n+\t\texit(128);\n \tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);\n \tadd_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);\n \tfree_commit_list(bases);\n@@ -2068,14 +2069,17 @@ static int handle_dotdot_1(const char *arg, char *dotdot,\n \t} else {\n \t\t/* A...B -- find merge bases between the two */\n \t\tstruct commit *a, *b;\n-\t\tstruct commit_list *exclude;\n+\t\tstruct commit_list *exclude = NULL;\n \n \t\ta = lookup_commit_reference(revs->repo, &a_obj->oid);\n \t\tb = lookup_commit_reference(revs->repo, &b_obj->oid);\n \t\tif (!a || !b)\n \t\t\treturn dotdot_missing(arg, dotdot, revs, symmetric);\n \n-\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) {\n+\t\t\tfree_commit_list(exclude);\n+\t\t\treturn -1;\n+\t\t}\n \t\tadd_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,\n \t\t\t\t     flags_exclude);\n \t\tadd_pending_commit_list(revs, exclude, flags_exclude);\ndiff --git a/sequencer.c b/sequencer.c\nindex d584cac8ed9..4417f2f1956 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3913,7 +3913,7 @@ static int do_merge(struct repository *r,\n \tint run_commit_flags = 0;\n \tstruct strbuf ref_name = STRBUF_INIT;\n \tstruct commit *head_commit, *merge_commit, *i;\n-\tstruct commit_list *bases, *j;\n+\tstruct commit_list *bases = NULL, *j;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts.nr &&\n \t\t(!opts->strategy ||\n@@ -4139,7 +4139,11 @@ static int do_merge(struct repository *r,\n \t}\n \n \tmerge_commit = to_merge->item;\n-\tbases = repo_get_merge_bases(r, head_commit, merge_commit);\n+\tif (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n \tif (bases && oideq(&merge_commit->object.oid,\n \t\t\t   &bases->item->object.oid)) {\n \t\tret = 0;\ndiff --git a/submodule.c b/submodule.c\nindex e603a19a876..04931a5474b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -595,7 +595,12 @@ static void show_submodule_header(struct diff_options *o,\n \t     (!is_null_oid(two) && !*right))\n \t\tmessage = \"(commits not present)\";\n \n-\t*merge_bases = repo_get_merge_bases(sub, *left, *right);\n+\t*merge_bases = NULL;\n+\tif (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) {\n+\t\tmessage = \"(corrupt repository)\";\n+\t\tgoto output_header;\n+\t}\n+\n \tif (*merge_bases) {\n \t\tif ((*merge_bases)->item == *left)\n \t\t\tfast_forward = 1;\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex b2c8a43fce3..5d1e7aca4c8 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -945,4 +945,16 @@ test_expect_success 'check the input format when --stdin is passed' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out on missing commits as well' '\n+\tgit init --bare missing-commit.git &&\n+\tgit rev-list --objects side1 side3 >list-including-initial &&\n+\tgrep -v ^$(git rev-parse side1^) <list-including-initial >list &&\n+\tgit pack-objects missing-commit.git/objects/pack/missing-initial <list &&\n+\tside1=$(git rev-parse side1) &&\n+\tside3=$(git rev-parse side3) &&\n+\ttest_must_fail git --git-dir=missing-commit.git \\\n+\t\tmerge-tree --allow-unrelated-histories $side1 $side3 >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"489143","messageId":"602a7383f726c14bf7941653c2825b4c345247c8.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 10/11] repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:49Z","receivedAt":"2024-02-22T13:22:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many()` function is aware of\nthat, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext stop: `repo_get_merge_bases_dirty()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n bisect.c              |  7 ++++---\n builtin/log.c         | 13 +++++++------\n commit-reach.c        | 16 ++++++----------\n commit-reach.h        |  7 ++++---\n commit.c              |  7 ++++---\n t/helper/test-reach.c |  9 ++++++---\n 6 files changed, 31 insertions(+), 28 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 1be8e0a2711..2018466d69f 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -851,10 +851,11 @@ static void handle_skipped_merge_base(const struct object_id *mb)\n static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)\n {\n \tenum bisect_error res = BISECT_OK;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \n-\tresult = repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n-\t\t\t\t\t   rev + 1);\n+\tif (repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n+\t\t\t\t      rev + 1, &result) < 0)\n+\t\texit(128);\n \n \tfor (; result; result = result->next) {\n \t\tconst struct object_id *mb = &result->item->object.oid;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex befafd6ae04..c75790a7cec 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1656,7 +1656,7 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\tstruct branch *curr_branch = branch_get(NULL);\n \t\tconst char *upstream = branch_get_upstream(curr_branch, NULL);\n \t\tif (upstream) {\n-\t\t\tstruct commit_list *base_list;\n+\t\t\tstruct commit_list *base_list = NULL;\n \t\t\tstruct commit *commit;\n \t\t\tstruct object_id oid;\n \n@@ -1667,11 +1667,12 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tcommit = lookup_commit_or_die(&oid, \"upstream base\");\n-\t\t\tbase_list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t      commit, total,\n-\t\t\t\t\t\t\t      list);\n-\t\t\t/* There should be one and only one merge base. */\n-\t\t\tif (!base_list || base_list->next) {\n+\t\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t\t      commit, total,\n+\t\t\t\t\t\t      list,\n+\t\t\t\t\t\t      &base_list) < 0 ||\n+\t\t\t    /* There should be one and only one merge base. */\n+\t\t\t    !base_list || base_list->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"could not find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/commit-reach.c b/commit-reach.c\nindex fa21a8f2f6b..954a05399f1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -462,17 +462,13 @@ static int get_merge_bases_many_0(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one,\n-\t\t\t\t\t      int n,\n-\t\t\t\t\t      struct commit **twos)\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one,\n+\t\t\t      int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 4690b6ecd0c..458043f4d58 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -13,9 +13,10 @@ int repo_get_merge_bases(struct repository *r,\n \t\t\t struct commit *rev1,\n \t\t\t struct commit *rev2,\n \t\t\t struct commit_list **result);\n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one, int n,\n-\t\t\t\t\t      struct commit **twos);\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one, int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\ndiff --git a/commit.c b/commit.c\nindex 8405d7c3fce..00add5d81c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1054,7 +1054,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n {\n \tstruct object_id oid;\n \tstruct rev_collect revs;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint i;\n \tstruct commit *ret = NULL;\n \tchar *full_refname;\n@@ -1079,8 +1079,9 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n \tfor (i = 0; i < revs.nr; i++)\n \t\trevs.commit[i]->object.flags &= ~TMP_MARK;\n \n-\tbases = repo_get_merge_bases_many(the_repository, commit, revs.nr,\n-\t\t\t\t\t  revs.commit);\n+\tif (repo_get_merge_bases_many(the_repository, commit, revs.nr,\n+\t\t\t\t      revs.commit, &bases) < 0)\n+\t\texit(128);\n \n \t/*\n \t * There should be one and only one merge base, when we found\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex aa816e168ea..84ee9da8681 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -117,9 +117,12 @@ int cmd__reach(int ac, const char **av)\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-\t\tstruct commit_list *list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t\t     A, X_nr,\n-\t\t\t\t\t\t\t\t     X_array);\n+\t\tstruct commit_list *list = NULL;\n+\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t      A, X_nr,\n+\t\t\t\t\t      X_array,\n+\t\t\t\t\t      &list) < 0)\n+\t\t\texit(128);\n \t\tprintf(\"%s(A,X):\\n\", av[1]);\n \t\tprint_sorted_commit_ids(list);\n \t} else if (!strcmp(av[1], \"reduce_heads\")) {\n-- \ngitgitgadget\n\n"},{"id":"489144","messageId":"b11879edb732263f2f77bc6a38b79db6a8f93826.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 09/11] get_octopus_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:48Z","receivedAt":"2024-02-22T13:22:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `get_merge_bases()` macro) is aware of that, too.\n\nNaturally, the callers need to be adjusted now, too.\n\nNext step: adjust `repo_get_merge_bases_many()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  8 ++++++--\n builtin/merge.c      |  6 +++++-\n builtin/pull.c       |  5 +++--\n commit-reach.c       | 20 +++++++++++---------\n commit-reach.h       |  2 +-\n 5 files changed, 26 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 0308fd73289..2edffc5487e 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -77,13 +77,17 @@ static int handle_independent(int count, const char **args)\n static int handle_octopus(int count, const char **args, int show_all)\n {\n \tstruct commit_list *revs = NULL;\n-\tstruct commit_list *result, *rev;\n+\tstruct commit_list *result = NULL, *rev;\n \tint i;\n \n \tfor (i = count - 1; i >= 0; i--)\n \t\tcommit_list_insert(get_commit_reference(args[i]), &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0) {\n+\t\tfree_commit_list(revs);\n+\t\tfree_commit_list(result);\n+\t\treturn 128;\n+\t}\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ac9d58adc29..94c5b693972 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1526,7 +1526,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n-\t\tcommon = get_octopus_merge_bases(list);\n+\t\tif (get_octopus_merge_bases(list, &common) < 0) {\n+\t\t\tfree(list);\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n \t\tfree(list);\n \t}\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e6f2942c0c5..0c5a55f2f4d 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -820,7 +820,7 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tconst struct object_id *merge_head,\n \t\tconst struct object_id *fork_point)\n {\n-\tstruct commit_list *revs = NULL, *result;\n+\tstruct commit_list *revs = NULL, *result = NULL;\n \n \tcommit_list_insert(lookup_commit_reference(the_repository, curr_head),\n \t\t\t   &revs);\n@@ -830,7 +830,8 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tcommit_list_insert(lookup_commit_reference(the_repository, fork_point),\n \t\t\t\t   &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0)\n+\t\texit(128);\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/commit-reach.c b/commit-reach.c\nindex 10e625ff51b..fa21a8f2f6b 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -176,24 +176,26 @@ static int merge_bases_many(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)\n {\n-\tstruct commit_list *i, *j, *k, *ret = NULL;\n+\tstruct commit_list *i, *j, *k;\n \n \tif (!in)\n-\t\treturn ret;\n+\t\treturn 0;\n \n-\tcommit_list_insert(in->item, &ret);\n+\tcommit_list_insert(in->item, result);\n \n \tfor (i = in->next; i; i = i->next) {\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n-\t\tfor (j = ret; j; j = j->next) {\n+\t\tfor (j = *result; j; j = j->next) {\n \t\t\tstruct commit_list *bases = NULL;\n \t\t\tif (repo_get_merge_bases(the_repository, i->item,\n \t\t\t\t\t\t j->item, &bases) < 0) {\n \t\t\t\tfree_commit_list(bases);\n-\t\t\t\treturn NULL;\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n+\t\t\t\treturn -1;\n \t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n@@ -202,10 +204,10 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\t\tfor (k = bases; k; k = k->next)\n \t\t\t\tend = k;\n \t\t}\n-\t\tfree_commit_list(ret);\n-\t\tret = new_commits;\n+\t\tfree_commit_list(*result);\n+\t\t*result = new_commits;\n \t}\n-\treturn ret;\n+\treturn 0;\n }\n \n static int remove_redundant_no_gen(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 2c6fcdd34f6..4690b6ecd0c 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -21,7 +21,7 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\n \t\t\t\t\t\t    struct commit **twos);\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in);\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n int repo_is_descendant_of(struct repository *r,\n \t\t\t  struct commit *commit,\n-- \ngitgitgadget\n\n"},{"id":"489145","messageId":"96850ed2d690adc58dd7866f984a04480e49fec0.1708608110.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v2 11/11] repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-22T13:21:50Z","receivedAt":"2024-02-22T13:22:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many_dirty()` function is\naware of that, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  9 ++++++---\n commit-reach.c       | 16 ++++++----------\n commit-reach.h       |  7 ++++---\n 3 files changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 2edffc5487e..a8a1ca53968 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -13,10 +13,13 @@\n \n static int show_merge_base(struct commit **rev, int rev_nr, int show_all)\n {\n-\tstruct commit_list *result, *r;\n+\tstruct commit_list *result = NULL, *r;\n \n-\tresult = repo_get_merge_bases_many_dirty(the_repository, rev[0],\n-\t\t\t\t\t\t rev_nr - 1, rev + 1);\n+\tif (repo_get_merge_bases_many_dirty(the_repository, rev[0],\n+\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn -1;\n+\t}\n \n \tif (!result)\n \t\treturn 1;\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 954a05399f1..2c69cb83d6f 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -471,17 +471,13 @@ int repo_get_merge_bases_many(struct repository *r,\n \treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one,\n-\t\t\t\t\t\t    int n,\n-\t\t\t\t\t\t    struct commit **twos)\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one,\n+\t\t\t\t    int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 0, result);\n }\n \n int repo_get_merge_bases(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 458043f4d58..bf63cc468fd 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -18,9 +18,10 @@ int repo_get_merge_bases_many(struct repository *r,\n \t\t\t      struct commit **twos,\n \t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t\t    struct commit **twos);\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one, int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result);\n \n int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n-- \ngitgitgadget\n"},{"id":"489270","messageId":"xmqqle7asnu9.fsf@gitster.g","threadId":"60905","inReplyTo":"4dd214f91d4783f29b03908cc0034156253889a7.1708608110.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-24T00:33:18Z","receivedAt":"2024-02-24T00:33:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> Some functions in Git's source code follow the convention that returning\n> a negative value indicates a fatal error, e.g. repository corruption.\n>\n> Let's use this convention in `repo_in_merge_bases()` to report when one\n> of the specified commits is missing (i.e. when `repo_parse_commit()`\n> reports an error).\n>\n> Also adjust the callers of `repo_in_merge_bases()` to handle such\n> negative return values.\n\nAll of the above makes sense, but I have to wonder if this hunk\nshould rather want to be part of the previous step:\n\n> @@ -486,10 +488,10 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n>  \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n>  \n>  \tif (repo_parse_commit(r, commit))\n> -\t\treturn ret;\n> +\t\treturn ignore_missing_commits ? 0 : -1;\n>  \tfor (i = 0; i < nr_reference; i++) {\n>  \t\tif (repo_parse_commit(r, reference[i]))\n> -\t\t\treturn ret;\n> +\t\t\treturn ignore_missing_commits ? 0 : -1;\n>  \n>  \t\tgeneration = commit_graph_generation(reference[i]);\n>  \t\tif (generation > max_generation)\n\nas this hunk is not about many callers of repo_in_merge_bases() that\nignored the return values, which are all fixed by this patch, but\nabout returning that error signal back to the caller.\n\nYes, I know you wrote in [02/11] that it does not change the\nbehaviour, and if you move this hunk to [02/11], it might change the\nbehaviour, but that is changing for the better.  Besides, adding a\nparameter \"ignore_missing\" to the function only to be ignored until\nthe next patch feels rather incomplete.\n\nThe other changes in this patch about its primary theme, fixing the\ncallers that used to ignore return values of repo_in_merge_bases(),\nall looked sensible.  This hunk somehow stood out like a sore thumb\nto me.\n\n\n"},{"id":"489359","messageId":"c27015da-0867-f417-4cd6-f4eaad3b66a9@gmx.de","threadId":"60905","inReplyTo":"xmqqle7asnu9.fsf@gitster.g","subject":"Re: [PATCH v2 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-02-26T09:34:44Z","receivedAt":"2024-02-26T09:34:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 23 Feb 2024, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > Some functions in Git's source code follow the convention that returning\n> > a negative value indicates a fatal error, e.g. repository corruption.\n> >\n> > Let's use this convention in `repo_in_merge_bases()` to report when one\n> > of the specified commits is missing (i.e. when `repo_parse_commit()`\n> > reports an error).\n> >\n> > Also adjust the callers of `repo_in_merge_bases()` to handle such\n> > negative return values.\n>\n> All of the above makes sense, but I have to wonder if this hunk\n> should rather want to be part of the previous step:\n>\n> > @@ -486,10 +488,10 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n> >  \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n> >\n> >  \tif (repo_parse_commit(r, commit))\n> > -\t\treturn ret;\n> > +\t\treturn ignore_missing_commits ? 0 : -1;\n> >  \tfor (i = 0; i < nr_reference; i++) {\n> >  \t\tif (repo_parse_commit(r, reference[i]))\n> > -\t\t\treturn ret;\n> > +\t\t\treturn ignore_missing_commits ? 0 : -1;\n> >\n> >  \t\tgeneration = commit_graph_generation(reference[i]);\n> >  \t\tif (generation > max_generation)\n>\n> as this hunk is not about many callers of repo_in_merge_bases() that\n> ignored the return values, which are all fixed by this patch, but\n> about returning that error signal back to the caller.\n>\n> Yes, I know you wrote in [02/11] that it does not change the\n> behaviour, and if you move this hunk to [02/11], it might change the\n> behaviour, but that is changing for the better.\n\nI wanted 2/11 to be trivial to review, and therefore specifically wanted\nbehavior not to change just yet: At least when I review patches, this\ninformation helps me assess the correctness of the patch because I have a\ndifferent pair of glasses on, so to say.\n\n> Besides, adding a parameter \"ignore_missing\" to the function only to be\n> ignored until the next patch feels rather incomplete.\n\nBy that reasoning, the entire patch series should be squashed into a\nsingle patch, as the missing commits will only be handled properly if all\n11 patches are applied ;-)\n\nSeriously again, I designed this patch series in a way where it builds up\nincrementally, adding preparations here and there, in as easily reviewable\na shape as I could, until the final patch wraps everything in a bow.\n\nI did this mostly to be able to convince myself of the correctness of the\npatches because I sense such a vast opportunity for bugs to creep in.\n\n> The other changes in this patch about its primary theme, fixing the\n> callers that used to ignore return values of repo_in_merge_bases(),\n> all looked sensible.  This hunk somehow stood out like a sore thumb\n> to me.\n\nI have an idea. How about pulling out this hunk into its own patch? And\ninsert it between 2/11 and 3/11? That would probably make most sense, as\nit would make the patch series still (relatively) easy to review, and it\nwould not conflate the purpose of this hunk with the rest of 3/11's hunks.\n\nWhat do you think?\n\nCiao,\nJohannes\n"},{"id":"489414","messageId":"xmqqsf1fkna2.fsf@gitster.g","threadId":"60905","inReplyTo":"c27015da-0867-f417-4cd6-f4eaad3b66a9@gmx.de","subject":"Re: [PATCH v2 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-26T20:01:41Z","receivedAt":"2024-02-26T20:01:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> I have an idea. How about pulling out this hunk into its own patch? And\n> insert it between 2/11 and 3/11? That would probably make most sense, as\n> it would make the patch series still (relatively) easy to review, and it\n> would not conflate the purpose of this hunk with the rest of 3/11's hunks.\n>\n> What do you think?\n\nIt does not solve the primary issue that [2/11] feels incomplete and\nmakes readers wonder what the new parameter that is unused is about.\nSo unless you are squashing that small bit into [2/11], I do not think\nsuch a churn is worth that much.\n"},{"id":"489485","messageId":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v2.git.1708608110.gitgitgadget@gmail.com","subject":"[PATCH v3 00/11] The merge-base logic vs missing commit objects","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:06Z","receivedAt":"2024-02-27T13:28:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series is in the same spirit as what I proposed in\nhttps://lore.kernel.org/git/pull.1651.git.1707212981.gitgitgadget@gmail.com/,\nwhere I tackled missing tree objects. This here patch series tackles missing\ncommit objects instead: Git's merge-base logic handles these missing commit\nobjects as if there had not been any commit object at all, i.e. if two\ncommits' merge base commit is missing, they will be treated as if they had\nno common commit history at all, which is a bug. Those commit objects should\nnot be missing, of course, i.e. this is only a problem in corrupt\nrepositories.\n\nThis patch series is a bit more tricky than the \"missing tree objects\" one,\nthough: The function signature of quite a few functions need to be changed\nto allow callers to discern between \"missing commit object\" vs \"no\nmerge-base found\".\n\nAnd of course it gets even more tricky because in shallow and partial clones\nwe expect commit objects to be missing, and that must not be treated like an\nerror but the existing logic is actually desirable in those scenarios.\n\nI am deeply sorry both about the length of this patch series as well as\nhaving to lean so heavily on reviews on the Git mailing list.\n\nChanges since v2:\n\n * Moved a hunk from 3/11 to 2/11 that lets the repo_in_merge_bases_many()\n   function return an error if non-existing commits have been passed to it,\n   unless the ignore_missing_commits parameter is non-zero.\n * The end result is tree-same.\n\nChanges since v1:\n\n * Addressed a lot of memory leaks.\n * Reordered patch 2 and 3 to render the commit message's comment about\n   unchanged behavior true.\n * Fixed the incorrectly converted condition in the merge_submodule()\n   function.\n * The last patch (\"paint_down_to_common(): special-case shallow/partial\n   clones\") was dropped because it is not actually necessary, and the\n   explanation for that was added to the commit message of \"Prepare\n   paint_down_to_common() for handling shallow commits\".\n * An inconsistently-named variable i was renamed to be consistent with the\n   other variables called ret.\n\nJohannes Schindelin (11):\n  paint_down_to_common: plug two memory leaks\n  Prepare `repo_in_merge_bases_many()` to optionally expect missing\n    commits\n  Start reporting missing commits in `repo_in_merge_bases_many()`\n  Prepare `paint_down_to_common()` for handling shallow commits\n  commit-reach: start reporting errors in `paint_down_to_common()`\n  merge_bases_many(): pass on errors from `paint_down_to_common()`\n  get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n  get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n  repo_get_merge_bases_many_dirty(): pass on errors from\n    `merge_bases_many()`\n\n bisect.c                         |   7 +-\n builtin/branch.c                 |  12 +-\n builtin/fast-import.c            |   6 +-\n builtin/fetch.c                  |   2 +\n builtin/log.c                    |  30 +++--\n builtin/merge-base.c             |  23 +++-\n builtin/merge-tree.c             |   5 +-\n builtin/merge.c                  |  26 ++--\n builtin/pull.c                   |   9 +-\n builtin/rebase.c                 |   8 +-\n builtin/receive-pack.c           |   6 +-\n builtin/rev-parse.c              |   5 +-\n commit-reach.c                   | 209 ++++++++++++++++++++-----------\n commit-reach.h                   |  26 ++--\n commit.c                         |   7 +-\n diff-lib.c                       |   5 +-\n http-push.c                      |   5 +-\n log-tree.c                       |   5 +-\n merge-ort.c                      |  87 +++++++++++--\n merge-recursive.c                |  58 +++++++--\n notes-merge.c                    |   3 +-\n object-name.c                    |   7 +-\n remote.c                         |   2 +-\n revision.c                       |  12 +-\n sequencer.c                      |   8 +-\n shallow.c                        |  21 ++--\n submodule.c                      |   7 +-\n t/helper/test-reach.c            |  11 +-\n t/t4301-merge-tree-write-tree.sh |  12 ++\n 29 files changed, 441 insertions(+), 183 deletions(-)\n\n\nbase-commit: 564d0252ca632e0264ed670534a51d18a689ef5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1657%2Fdscho%2Fmerge-base-and-missing-objects-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1657/dscho/merge-base-and-missing-objects-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1657\n\nRange-diff vs v2:\n\n  1:  6e4e409cd43 =  1:  6e4e409cd43 paint_down_to_common: plug two memory leaks\n  2:  5c16becfb9b !  2:  f68d77c6123 Prepare `repo_in_merge_bases_many()` to optionally expect missing commits\n     @@ Commit message\n          passed to `repo_in_merge_bases_many()` to trigger this behavior, and use\n          it in the two callers in `shallow.c`.\n      \n     -    This does not change behavior in this commit, but prepares in an\n     -    easy-to-review way for the upcoming changes that will make the merge\n     -    base logic more stringent with regards to missing commit objects.\n     +    This commit changes behavior slightly: unless called from the\n     +    `shallow.c` functions that set the `ignore_missing_commits` bit, any\n     +    non-existing tip commit that is passed to `repo_in_merge_bases_many()`\n     +    will now result in an error.\n     +\n     +    Note: When encountering missing commits while traversing the commit\n     +    history in search for merge bases, with this commit there won't be a\n     +    change in behavior just yet, their children will still be interpreted as\n     +    root commits. This bug will get fixed by follow-up commits.\n      \n          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n      \n     @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n       {\n       \tstruct commit_list *bases;\n       \tint ret = 0, i;\n     + \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n     + \n     + \tif (repo_parse_commit(r, commit))\n     +-\t\treturn ret;\n     ++\t\treturn ignore_missing_commits ? 0 : -1;\n     + \tfor (i = 0; i < nr_reference; i++) {\n     + \t\tif (repo_parse_commit(r, reference[i]))\n     +-\t\t\treturn ret;\n     ++\t\t\treturn ignore_missing_commits ? 0 : -1;\n     + \n     + \t\tgeneration = commit_graph_generation(reference[i]);\n     + \t\tif (generation > max_generation)\n      \n       ## commit-reach.h ##\n      @@ commit-reach.h: int repo_in_merge_bases(struct repository *r,\n  3:  4dd214f91d4 !  3:  0aaa224b5db Start reporting missing commits in `repo_in_merge_bases_many()`\n     @@ commit-reach.c: int repo_is_descendant_of(struct repository *r,\n       \t\t}\n       \t\treturn 0;\n       \t}\n     -@@ commit-reach.c: int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n     - \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n     - \n     - \tif (repo_parse_commit(r, commit))\n     --\t\treturn ret;\n     -+\t\treturn ignore_missing_commits ? 0 : -1;\n     - \tfor (i = 0; i < nr_reference; i++) {\n     - \t\tif (repo_parse_commit(r, reference[i]))\n     --\t\t\treturn ret;\n     -+\t\t\treturn ignore_missing_commits ? 0 : -1;\n     - \n     - \t\tgeneration = commit_graph_generation(reference[i]);\n     - \t\tif (generation > max_generation)\n      @@ commit-reach.c: int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)\n       \tcommit_list_insert(old_commit, &old_commit_list);\n       \tret = repo_is_descendant_of(the_repository,\n  4:  53bdeddb4cb =  4:  84e7fbc07e0 Prepare `paint_down_to_common()` for handling shallow commits\n  5:  ec3ebf0ed17 =  5:  85332b58c37 commit-reach: start reporting errors in `paint_down_to_common()`\n  6:  05756fbf71a =  6:  2ae6a54dd59 merge_bases_many(): pass on errors from `paint_down_to_common()`\n  7:  e3d37a326e5 =  7:  4321795102d get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n  8:  9ca504525b9 =  8:  35545c4b777 repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n  9:  b11879edb73 =  9:  a963058d2ba get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n 10:  602a7383f72 = 10:  c3bed9c8500 repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n 11:  96850ed2d69 = 11:  bdbf47ae505 repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`\n\n-- \ngitgitgadget\n"},{"id":"489486","messageId":"6e4e409cd43bcf445f811e171958ef4a169a0163.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 01/11] paint_down_to_common: plug two memory leaks","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:07Z","receivedAt":"2024-02-27T13:28:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen a commit is missing, we return early (currently pretending that no\nmerge basis could be found in that case). At that stage, it is possible\nthat a merge base could have been found already, and added to the\n`result`, which is now leaked.\n\nThe priority queue has a similar issue: There might still be a commit in\nthat queue.\n\nLet's release both, to address the potential memory leaks.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex a868a575ea1..7ea916f9ebd 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -105,8 +105,11 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tparents = parents->next;\n \t\t\tif ((p->object.flags & flags) == flags)\n \t\t\t\tcontinue;\n-\t\t\tif (repo_parse_commit(r, p))\n+\t\t\tif (repo_parse_commit(r, p)) {\n+\t\t\t\tclear_prio_queue(&queue);\n+\t\t\t\tfree_commit_list(result);\n \t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n \t\t}\n-- \ngitgitgadget\n\n"},{"id":"489488","messageId":"f68d77c612398368f860592a756b95c4d64d3b53.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 02/11] Prepare `repo_in_merge_bases_many()` to optionally expect missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:08Z","receivedAt":"2024-02-27T13:28:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nCurrently this function treats unrelated commit histories the same way\nas commit histories with missing commit objects.\n\nTypically, missing commit objects constitute a corrupt repository,\nthough, and should be reported as such. The next commits will make it\nso, but there is one exception: In `git fetch --update-shallow` we\n_expect_ commit objects to be missing, and we do want to treat the\nnow-incomplete commit histories as unrelated.\n\nTo allow for that, let's introduce an additional parameter that is\npassed to `repo_in_merge_bases_many()` to trigger this behavior, and use\nit in the two callers in `shallow.c`.\n\nThis commit changes behavior slightly: unless called from the\n`shallow.c` functions that set the `ignore_missing_commits` bit, any\nnon-existing tip commit that is passed to `repo_in_merge_bases_many()`\nwill now result in an error.\n\nNote: When encountering missing commits while traversing the commit\nhistory in search for merge bases, with this commit there won't be a\nchange in behavior just yet, their children will still be interpreted as\nroot commits. This bug will get fixed by follow-up commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c        | 9 +++++----\n commit-reach.h        | 3 ++-\n remote.c              | 2 +-\n shallow.c             | 5 +++--\n t/helper/test-reach.c | 2 +-\n 5 files changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7ea916f9ebd..5c1b5256598 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -467,7 +467,7 @@ int repo_is_descendant_of(struct repository *r,\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n+\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n \t\t\t\treturn 1;\n \t\t}\n \t\treturn 0;\n@@ -478,17 +478,18 @@ int repo_is_descendant_of(struct repository *r,\n  * Is \"commit\" an ancestor of one of the \"references\"?\n  */\n int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference)\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits)\n {\n \tstruct commit_list *bases;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n \tif (repo_parse_commit(r, commit))\n-\t\treturn ret;\n+\t\treturn ignore_missing_commits ? 0 : -1;\n \tfor (i = 0; i < nr_reference; i++) {\n \t\tif (repo_parse_commit(r, reference[i]))\n-\t\t\treturn ret;\n+\t\t\treturn ignore_missing_commits ? 0 : -1;\n \n \t\tgeneration = commit_graph_generation(reference[i]);\n \t\tif (generation > max_generation)\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 35c4da49481..68f81549a44 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -30,7 +30,8 @@ int repo_in_merge_bases(struct repository *r,\n \t\t\tstruct commit *reference);\n int repo_in_merge_bases_many(struct repository *r,\n \t\t\t     struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference);\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits);\n \n /*\n  * Takes a list of commits and returns a new list where those\ndiff --git a/remote.c b/remote.c\nindex abb24822beb..763c80f4a7d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2675,7 +2675,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n \t\tif (MERGE_BASES_BATCH_SIZE < size)\n \t\t\tsize = MERGE_BASES_BATCH_SIZE;\n \n-\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))\n+\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk, 0)))\n \t\t\tbreak;\n \t}\n \ndiff --git a/shallow.c b/shallow.c\nindex ac728cdd778..dfcc1f86a7f 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -797,7 +797,7 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tfor (j = 0; j < bitmap_nr; j++)\n \t\t\tif (bitmap[0][j] &&\n \t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n+\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n \t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n \t\t\t\tdst++;\n \t\t\t\tbreak;\n@@ -828,7 +828,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\tsi->reachable[c] = repo_in_merge_bases_many(the_repository,\n \t\t\t\t\t\t\t    commit,\n \t\t\t\t\t\t\t    si->nr_commits,\n-\t\t\t\t\t\t\t    si->commits);\n+\t\t\t\t\t\t\t    si->commits,\n+\t\t\t\t\t\t\t    1);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 3e173399a00..aa816e168ea 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -113,7 +113,7 @@ int cmd__reach(int ac, const char **av)\n \t\t       repo_in_merge_bases(the_repository, A, B));\n \telse if (!strcmp(av[1], \"in_merge_bases_many\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1],\n-\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array));\n+\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-- \ngitgitgadget\n\n"},{"id":"489487","messageId":"84e7fbc07e08956e6c493baf499fee455887b16c.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:10Z","receivedAt":"2024-02-27T13:28:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen `git fetch --update-shallow` needs to test for commit ancestry, it\ncan naturally run into a missing object (e.g. if it is a parent of a\nshallow commit). To accommodate, the merge base logic will need to be\nable to handle this situation gracefully.\n\nCurrently, that logic pretends that a missing parent commit is\nequivalent to a missing parent commit, and for the purpose of\n`--update-shallow` that is exactly what we need it to do.\n\nTherefore, let's introduce a flag to indicate when that is precisely the\nlogic we want.\n\nWe need a flag, and cannot rely on `is_repository_shallow()` to indicate\nthat situation, because that function would return 0: There may not\nactually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\nshallow root with receive.updateshallow on\") and t5538.4 (\"add new\nshallow root with receive.updateshallow on\").\n\nNote: shallow commits' parents are set to `NULL` internally already,\ntherefore there is no need to special-case shallow repositories here, as\nthe merge-base logic will not try to access parent commits of shallow\ncommits.\n\nLikewise, partial clones aren't an issue either: If a commit is missing\nduring the revision walk in the merge-base logic, it is fetched via\n`promisor_remote_get_direct()`. And not only the single missing commit\nobject: Due to the way the \"promised\" objects are fetched (in\n`fetch_objects()` in `promisor-remote.c`, using `fetch\n--filter=blob:none`), there is no actual way to fetch a single commit\nobject, as the remote side will pass that commit OID to `pack-objects\n--revs [...]` which in turn passes it to `rev-list` which interprets\nthis as a commit _range_ instead of a single object. Therefore, in\npartial clones (unless they are shallow in addition), all commits\nreachable from a commit that is in the local object database are also\npresent in that local database.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5ff71d72d51..7112b10eeea 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -53,7 +53,8 @@ static int queue_has_nonstale(struct prio_queue *queue)\n static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t\t\tstruct commit *one, int n,\n \t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation)\n+\t\t\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\t\t\tint ignore_missing_commits)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n \tstruct commit_list *result = NULL;\n@@ -108,6 +109,13 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n \t\t\t\tfree_commit_list(result);\n+\t\t\t\t/*\n+\t\t\t\t * At this stage, we know that the commit is\n+\t\t\t\t * missing: `repo_parse_commit()` uses\n+\t\t\t\t * `OBJECT_INFO_DIE_IF_CORRUPT` and therefore\n+\t\t\t\t * corrupt commits would already have been\n+\t\t\t\t * dispatched with a `die()`.\n+\t\t\t\t */\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n@@ -143,7 +151,7 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0);\n+\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -214,7 +222,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n \t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation);\n+\t\t\t\t\t      work, min_generation, 0);\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -504,7 +512,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \n \tbases = paint_down_to_common(r, commit,\n \t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation);\n+\t\t\t\t     generation, ignore_missing_commits);\n \tif (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n-- \ngitgitgadget\n\n"},{"id":"489490","messageId":"0aaa224b5db2e78c1848ab76f5b8b73331fe9cc9.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:09Z","receivedAt":"2024-02-27T13:28:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSome functions in Git's source code follow the convention that returning\na negative value indicates a fatal error, e.g. repository corruption.\n\nLet's use this convention in `repo_in_merge_bases()` to report when one\nof the specified commits is missing (i.e. when `repo_parse_commit()`\nreports an error).\n\nAlso adjust the callers of `repo_in_merge_bases()` to handle such\nnegative return values.\n\nNote: As of this patch, errors are returned only if any of the specified\nmerge heads is missing. Over the course of the next patches, missing\ncommits will also be reported by the `paint_down_to_common()` function,\nwhich is called by `repo_in_merge_bases_many()`, and those errors will\nbe properly propagated back to the caller at that stage.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch.c       | 12 +++++--\n builtin/fast-import.c  |  6 +++-\n builtin/fetch.c        |  2 ++\n builtin/log.c          |  7 ++--\n builtin/merge-base.c   |  6 +++-\n builtin/pull.c         |  4 +++\n builtin/receive-pack.c |  6 +++-\n commit-reach.c         |  8 +++--\n http-push.c            |  5 ++-\n merge-ort.c            | 81 ++++++++++++++++++++++++++++++++++++------\n merge-recursive.c      | 54 +++++++++++++++++++++++-----\n shallow.c              | 18 ++++++----\n 12 files changed, 173 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e7ee9bd0f15..7f9e79237f3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -161,6 +161,8 @@ static int branch_merged(int kind, const char *name,\n \n \tmerged = reference_rev ? repo_in_merge_bases(the_repository, rev,\n \t\t\t\t\t\t     reference_rev) : 0;\n+\tif (merged < 0)\n+\t\texit(128);\n \n \t/*\n \t * After the safety valve is fully redefined to \"check with\n@@ -169,9 +171,13 @@ static int branch_merged(int kind, const char *name,\n \t * any of the following code, but during the transition period,\n \t * a gentle reminder is in order.\n \t */\n-\tif ((head_rev != reference_rev) &&\n-\t    (head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != merged) {\n-\t\tif (merged)\n+\tif (head_rev != reference_rev) {\n+\t\tint expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0;\n+\t\tif (expect < 0)\n+\t\t\texit(128);\n+\t\tif (expect == merged)\n+\t\t\t; /* okay */\n+\t\telse if (merged)\n \t\t\twarning(_(\"deleting branch '%s' that has been merged to\\n\"\n \t\t\t\t\"         '%s', but not yet merged to HEAD\"),\n \t\t\t\tname, reference_name);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 444f41cf8ca..14c2efa88fc 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1625,6 +1625,7 @@ static int update_branch(struct branch *b)\n \t\toidclr(&old_oid);\n \tif (!force_update && !is_null_oid(&old_oid)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n+\t\tint ret;\n \n \t\told_cmit = lookup_commit_reference_gently(the_repository,\n \t\t\t\t\t\t\t  &old_oid, 0);\n@@ -1633,7 +1634,10 @@ static int update_branch(struct branch *b)\n \t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n \n-\t\tif (!repo_in_merge_bases(the_repository, old_cmit, new_cmit)) {\n+\t\tret = repo_in_merge_bases(the_repository, old_cmit, new_cmit);\n+\t\tif (ret < 0)\n+\t\t\texit(128);\n+\t\tif (!ret) {\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, oid_to_hex(&b->oid),\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex fd134ba74d9..0584a1f8b64 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -978,6 +978,8 @@ static int update_local_ref(struct ref *ref,\n \t\tuint64_t t_before = getnanotime();\n \t\tfast_forward = repo_in_merge_bases(the_repository, current,\n \t\t\t\t\t\t   updated);\n+\t\tif (fast_forward < 0)\n+\t\t\texit(128);\n \t\tforced_updates_ms += (getnanotime() - t_before) / 1000000;\n \t} else {\n \t\tfast_forward = 1;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ba775d7b5cf..1705da71aca 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1623,7 +1623,7 @@ static struct commit *get_base_commit(const char *base_commit,\n {\n \tstruct commit *base = NULL;\n \tstruct commit **rev;\n-\tint i = 0, rev_nr = 0, auto_select, die_on_failure;\n+\tint i = 0, rev_nr = 0, auto_select, die_on_failure, ret;\n \n \tswitch (auto_base) {\n \tcase AUTO_BASE_NEVER:\n@@ -1723,7 +1723,10 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\trev_nr = DIV_ROUND_UP(rev_nr, 2);\n \t}\n \n-\tif (!repo_in_merge_bases(the_repository, base, rev[0])) {\n+\tret = repo_in_merge_bases(the_repository, base, rev[0]);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (!ret) {\n \t\tif (die_on_failure) {\n \t\t\tdie(_(\"base commit should be the ancestor of revision list\"));\n \t\t} else {\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex e68b7fe45d7..0308fd73289 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -103,12 +103,16 @@ static int handle_octopus(int count, const char **args, int show_all)\n static int handle_is_ancestor(int argc, const char **argv)\n {\n \tstruct commit *one, *two;\n+\tint ret;\n \n \tif (argc != 2)\n \t\tdie(\"--is-ancestor takes exactly two commits\");\n \tone = get_commit_reference(argv[0]);\n \ttwo = get_commit_reference(argv[1]);\n-\tif (repo_in_merge_bases(the_repository, one, two))\n+\tret = repo_in_merge_bases(the_repository, one, two);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (ret)\n \t\treturn 0;\n \telse\n \t\treturn 1;\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex be2b2c9ebc9..e6f2942c0c5 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -931,6 +931,8 @@ static int get_can_ff(struct object_id *orig_head,\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n \tret = repo_is_descendant_of(the_repository, merge_head, list);\n \tfree_commit_list(list);\n+\tif (ret < 0)\n+\t\texit(128);\n \treturn ret;\n }\n \n@@ -955,6 +957,8 @@ static int already_up_to_date(struct object_id *orig_head,\n \t\tcommit_list_insert(theirs, &list);\n \t\tok = repo_is_descendant_of(the_repository, ours, list);\n \t\tfree_commit_list(list);\n+\t\tif (ok < 0)\n+\t\t\texit(128);\n \t\tif (!ok)\n \t\t\treturn 0;\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8c4f0cb90a9..956fea6293e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1546,6 +1546,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t    starts_with(name, \"refs/heads/\")) {\n \t\tstruct object *old_object, *new_object;\n \t\tstruct commit *old_commit, *new_commit;\n+\t\tint ret2;\n \n \t\told_object = parse_object(the_repository, old_oid);\n \t\tnew_object = parse_object(the_repository, new_oid);\n@@ -1559,7 +1560,10 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t}\n \t\told_commit = (struct commit *)old_object;\n \t\tnew_commit = (struct commit *)new_object;\n-\t\tif (!repo_in_merge_bases(the_repository, old_commit, new_commit)) {\n+\t\tret2 = repo_in_merge_bases(the_repository, old_commit, new_commit);\n+\t\tif (ret2 < 0)\n+\t\t\texit(128);\n+\t\tif (!ret2) {\n \t\t\trp_error(\"denying non-fast-forward %s\"\n \t\t\t\t \" (you should pull first)\", name);\n \t\t\tret = \"non-fast-forward\";\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5c1b5256598..5ff71d72d51 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -464,11 +464,13 @@ int repo_is_descendant_of(struct repository *r,\n \t} else {\n \t\twhile (with_commit) {\n \t\t\tstruct commit *other;\n+\t\t\tint ret;\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n-\t\t\t\treturn 1;\n+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n \t\t}\n \t\treturn 0;\n \t}\n@@ -598,6 +600,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)\n \tcommit_list_insert(old_commit, &old_commit_list);\n \tret = repo_is_descendant_of(the_repository,\n \t\t\t\t    new_commit, old_commit_list);\n+\tif (ret < 0)\n+\t\texit(128);\n \tfree_commit_list(old_commit_list);\n \treturn ret;\n }\ndiff --git a/http-push.c b/http-push.c\nindex a704f490fdb..24c16a4f5ff 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1576,8 +1576,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote)\n \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n \t\t\t\t\t\t     remote->name);\n+\tint ret = repo_in_merge_bases(the_repository, branch, head);\n \n-\treturn repo_in_merge_bases(the_repository, branch, head);\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret;\n }\n \n static int delete_remote_branch(const char *pattern, int force)\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 6491070d965..9f3af46333a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -544,6 +544,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n \tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n+\tCONFLICT_SUBMODULE_CORRUPT,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -596,7 +597,9 @@ static const char *type_short_descriptions[] = {\n \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n \t\t\"CONFLICT (submodule may have rewinds)\",\n \t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n-\t\t\"CONFLICT (submodule lacks merge base)\"\n+\t\t\"CONFLICT (submodule lacks merge base)\",\n+\t[CONFLICT_SUBMODULE_CORRUPT] =\n+\t\t\"CONFLICT (submodule corrupt)\"\n };\n \n struct logical_conflict_info {\n@@ -1710,7 +1713,14 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret > 0)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1725,9 +1735,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1749,7 +1767,7 @@ static int merge_submodule(struct merge_options *opt,\n {\n \tstruct repository subrepo;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1796,8 +1814,26 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s \"\n@@ -1807,7 +1843,16 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, b);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1816,7 +1861,16 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, a);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1841,6 +1895,13 @@ static int merge_submodule(struct merge_options *opt,\n \tparent_count = find_first_merges(&subrepo, path, commit_a, commit_b,\n \t\t\t\t\t &merges);\n \tswitch (parent_count) {\n+\tcase -1:\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tbreak;\n \tcase 0:\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n \t\t\t path, NULL, NULL, NULL,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3beb0801b1..0d931cc14ad 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1144,7 +1144,13 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1159,9 +1165,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1197,7 +1211,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   const struct object_id *b)\n {\n \tstruct repository subrepo;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_base, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1234,14 +1248,29 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (commits don't follow merge-base)\"), path);\n \t\tgoto cleanup;\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, b);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1254,7 +1283,12 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, a);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&o->oid,\n \t\t\t\t\t\t\t&a->oid,\n \t\t\t\t\t\t\t&b->oid);\n+\t\t\tif (result->clean < 0)\n+\t\t\t\treturn -1;\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n \t\t\tcase MERGE_VARIANT_NORMAL:\ndiff --git a/shallow.c b/shallow.c\nindex dfcc1f86a7f..f71496f35c3 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -795,12 +795,16 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tif (!*bitmap)\n \t\t\tcontinue;\n \t\tfor (j = 0; j < bitmap_nr; j++)\n-\t\t\tif (bitmap[0][j] &&\n-\t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n-\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n-\t\t\t\tdst++;\n-\t\t\t\tbreak;\n+\t\t\tif (bitmap[0][j]) {\n+\t\t\t\t/* Step 7, reachability test at commit level */\n+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\texit(128);\n+\t\t\t\tif (!ret) {\n+\t\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n+\t\t\t\t\tdst++;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t}\n \tinfo->nr_ours = dst;\n@@ -830,6 +834,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\t\t\t\t\t\t    si->nr_commits,\n \t\t\t\t\t\t\t    si->commits,\n \t\t\t\t\t\t\t    1);\n+\t\tif (si->reachable[c] < 0)\n+\t\t\texit(128);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\n-- \ngitgitgadget\n\n"},{"id":"489489","messageId":"85332b58c37717b5b8b6c826a2a3388dce3b0daa.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 05/11] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:11Z","receivedAt":"2024-02-27T13:28:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIf a commit cannot be parsed, it is currently ignored when looking for\nmerge bases. That's undesirable as the operation can pretend success in\na corrupt repository, even though the command should fail with an error\nmessage.\n\nLet's start at the bottom of the stack by teaching the\n`paint_down_to_common()` function to return an `int`: if negative, it\nindicates fatal error, if 0 success.\n\nThis requires a couple of callers to be adjusted accordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 66 ++++++++++++++++++++++++++++++++++----------------\n 1 file changed, 45 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7112b10eeea..9148a7dcbc0 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -50,14 +50,14 @@ static int queue_has_nonstale(struct prio_queue *queue)\n }\n \n /* all input commits in one and twos[] must have been parsed! */\n-static struct commit_list *paint_down_to_common(struct repository *r,\n-\t\t\t\t\t\tstruct commit *one, int n,\n-\t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation,\n-\t\t\t\t\t\tint ignore_missing_commits)\n+static int paint_down_to_common(struct repository *r,\n+\t\t\t\tstruct commit *one, int n,\n+\t\t\t\tstruct commit **twos,\n+\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\tint ignore_missing_commits,\n+\t\t\t\tstruct commit_list **result)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n-\tstruct commit_list *result = NULL;\n \tint i;\n \ttimestamp_t last_gen = GENERATION_NUMBER_INFINITY;\n \n@@ -66,8 +66,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \n \tone->object.flags |= PARENT1;\n \tif (!n) {\n-\t\tcommit_list_append(one, &result);\n-\t\treturn result;\n+\t\tcommit_list_append(one, result);\n+\t\treturn 0;\n \t}\n \tprio_queue_put(&queue, one);\n \n@@ -95,7 +95,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\tif (flags == (PARENT1 | PARENT2)) {\n \t\t\tif (!(commit->object.flags & RESULT)) {\n \t\t\t\tcommit->object.flags |= RESULT;\n-\t\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\t\tcommit_list_insert_by_date(commit, result);\n \t\t\t}\n \t\t\t/* Mark parents of a found merge stale */\n \t\t\tflags |= STALE;\n@@ -108,7 +108,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\tcontinue;\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n-\t\t\t\tfree_commit_list(result);\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n \t\t\t\t/*\n \t\t\t\t * At this stage, we know that the commit is\n \t\t\t\t * missing: `repo_parse_commit()` uses\n@@ -116,7 +117,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t * corrupt commits would already have been\n \t\t\t\t * dispatched with a `die()`.\n \t\t\t\t */\n-\t\t\t\treturn NULL;\n+\t\t\t\tif (ignore_missing_commits)\n+\t\t\t\t\treturn 0;\n+\t\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t\t     oid_to_hex(&p->object.oid));\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n@@ -124,7 +128,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t}\n \n \tclear_prio_queue(&queue);\n-\treturn result;\n+\treturn 0;\n }\n \n static struct commit_list *merge_bases_many(struct repository *r,\n@@ -151,7 +155,10 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n+\t\tfree_commit_list(list);\n+\t\treturn NULL;\n+\t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -205,7 +212,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \tfor (i = 0; i < cnt; i++)\n \t\trepo_parse_commit(r, array[i]);\n \tfor (i = 0; i < cnt; i++) {\n-\t\tstruct commit_list *common;\n+\t\tstruct commit_list *common = NULL;\n \t\ttimestamp_t min_generation = commit_graph_generation(array[i]);\n \n \t\tif (redundant[i])\n@@ -221,8 +228,16 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\tif (curr_generation < min_generation)\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n-\t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation, 0);\n+\t\tif (paint_down_to_common(r, array[i], filled,\n+\t\t\t\t\t work, min_generation, 0, &common)) {\n+\t\t\tclear_commit_marks(array[i], all_flags);\n+\t\t\tclear_commit_marks_many(filled, work, all_flags);\n+\t\t\tfree_commit_list(common);\n+\t\t\tfree(work);\n+\t\t\tfree(redundant);\n+\t\t\tfree(filled_index);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -422,6 +437,10 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tclear_commit_marks_many(n, twos, all_flags);\n \n \tcnt = remove_redundant(r, rslt, cnt);\n+\tif (cnt < 0) {\n+\t\tfree(rslt);\n+\t\treturn NULL;\n+\t}\n \tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n \t\tcommit_list_insert_by_date(rslt[i], &result);\n@@ -491,7 +510,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \t\t\t     int nr_reference, struct commit **reference,\n \t\t\t     int ignore_missing_commits)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n@@ -510,10 +529,11 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \tif (generation > max_generation)\n \t\treturn ret;\n \n-\tbases = paint_down_to_common(r, commit,\n-\t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation, ignore_missing_commits);\n-\tif (commit->object.flags & PARENT2)\n+\tif (paint_down_to_common(r, commit,\n+\t\t\t\t nr_reference, reference,\n+\t\t\t\t generation, ignore_missing_commits, &bases))\n+\t\tret = -1;\n+\telse if (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n \tclear_commit_marks_many(nr_reference, reference, all_flags);\n@@ -566,6 +586,10 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \t\t}\n \t}\n \tnum_head = remove_redundant(the_repository, array, num_head);\n+\tif (num_head < 0) {\n+\t\tfree(array);\n+\t\treturn NULL;\n+\t}\n \tfor (i = 0; i < num_head; i++)\n \t\ttail = &commit_list_insert(array[i], tail)->next;\n \tfree(array);\n-- \ngitgitgadget\n\n"},{"id":"489491","messageId":"2ae6a54dd596c3192b5be32f0e4e2605f4aac6bd.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 06/11] merge_bases_many(): pass on errors from `paint_down_to_common()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:12Z","receivedAt":"2024-02-27T13:28:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `paint_down_to_common()` function was just taught to indicate\nparsing errors, and now the `merge_bases_many()` function is aware of\nthat, too.\n\nOne tricky aspect is that `merge_bases_many()` parses commits of its\nown, but wants to gracefully handle the scenario where NULL is passed as\na merge head, returning the empty list of merge bases. The way this was\nhandled involved calling `repo_parse_commit(NULL)` and relying on it to\nreturn an error. This has to be done differently now so that we can\nhandle missing commits correctly by producing a fatal error.\n\nNext step: adjust the caller of `merge_bases_many()`:\n`get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 9148a7dcbc0..2c74583c8e0 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -131,41 +131,49 @@ static int paint_down_to_common(struct repository *r,\n \treturn 0;\n }\n \n-static struct commit_list *merge_bases_many(struct repository *r,\n-\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t    struct commit **twos)\n+static int merge_bases_many(struct repository *r,\n+\t\t\t    struct commit *one, int n,\n+\t\t\t    struct commit **twos,\n+\t\t\t    struct commit_list **result)\n {\n \tstruct commit_list *list = NULL;\n-\tstruct commit_list *result = NULL;\n \tint i;\n \n \tfor (i = 0; i < n; i++) {\n-\t\tif (one == twos[i])\n+\t\tif (one == twos[i]) {\n \t\t\t/*\n \t\t\t * We do not mark this even with RESULT so we do not\n \t\t\t * have to clean it up.\n \t\t\t */\n-\t\t\treturn commit_list_insert(one, &result);\n+\t\t\t*result = commit_list_insert(one, result);\n+\t\t\treturn 0;\n+\t\t}\n \t}\n \n+\tif (!one)\n+\t\treturn 0;\n \tif (repo_parse_commit(r, one))\n-\t\treturn NULL;\n+\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t     oid_to_hex(&one->object.oid));\n \tfor (i = 0; i < n; i++) {\n+\t\tif (!twos[i])\n+\t\t\treturn 0;\n \t\tif (repo_parse_commit(r, twos[i]))\n-\t\t\treturn NULL;\n+\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n \t}\n \n \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n \t\tfree_commit_list(list);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n \t\tif (!(commit->object.flags & STALE))\n-\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\tcommit_list_insert_by_date(commit, result);\n \t}\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n@@ -410,10 +418,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tresult = merge_bases_many(r, one, n, twos);\n+\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n+\t\treturn NULL;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n \t\t\treturn result;\n-- \ngitgitgadget\n\n"},{"id":"489492","messageId":"4321795102d5cc5f4e599cba0ff623b5eb31b5bd.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 07/11] get_merge_bases_many_0(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:13Z","receivedAt":"2024-02-27T13:28:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate\nparsing errors, and now the `get_merge_bases_many_0()` function is aware\nof that, too.\n\nNext step: adjust the callers of `get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 57 +++++++++++++++++++++++++++++++-------------------\n 1 file changed, 36 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 2c74583c8e0..5fa0abc7d1e 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -410,37 +410,38 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt\n \treturn remove_redundant_no_gen(r, array, cnt);\n }\n \n-static struct commit_list *get_merge_bases_many_0(struct repository *r,\n-\t\t\t\t\t\t  struct commit *one,\n-\t\t\t\t\t\t  int n,\n-\t\t\t\t\t\t  struct commit **twos,\n-\t\t\t\t\t\t  int cleanup)\n+static int get_merge_bases_many_0(struct repository *r,\n+\t\t\t\t  struct commit *one,\n+\t\t\t\t  int n,\n+\t\t\t\t  struct commit **twos,\n+\t\t\t\t  int cleanup,\n+\t\t\t\t  struct commit_list **result)\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n-\t\treturn NULL;\n+\tif (merge_bases_many(r, one, n, twos, result) < 0)\n+\t\treturn -1;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n-\t\t\treturn result;\n+\t\t\treturn 0;\n \t}\n-\tif (!result || !result->next) {\n+\tif (!*result || !(*result)->next) {\n \t\tif (cleanup) {\n \t\t\tclear_commit_marks(one, all_flags);\n \t\t\tclear_commit_marks_many(n, twos, all_flags);\n \t\t}\n-\t\treturn result;\n+\t\treturn 0;\n \t}\n \n \t/* There are more than one */\n-\tcnt = commit_list_count(result);\n+\tcnt = commit_list_count(*result);\n \tCALLOC_ARRAY(rslt, cnt);\n-\tfor (list = result, i = 0; list; list = list->next)\n+\tfor (list = *result, i = 0; list; list = list->next)\n \t\trslt[i++] = list->item;\n-\tfree_commit_list(result);\n+\tfree_commit_list(*result);\n+\t*result = NULL;\n \n \tclear_commit_marks(one, all_flags);\n \tclear_commit_marks_many(n, twos, all_flags);\n@@ -448,13 +449,12 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tcnt = remove_redundant(r, rslt, cnt);\n \tif (cnt < 0) {\n \t\tfree(rslt);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n-\tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n-\t\tcommit_list_insert_by_date(rslt[i], &result);\n+\t\tcommit_list_insert_by_date(rslt[i], result);\n \tfree(rslt);\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n@@ -462,7 +462,12 @@ struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      int n,\n \t\t\t\t\t      struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n@@ -470,14 +475,24 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    int n,\n \t\t\t\t\t\t    struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 0);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases(struct repository *r,\n \t\t\t\t\t struct commit *one,\n \t\t\t\t\t struct commit *two)\n {\n-\treturn get_merge_bases_many_0(r, one, 1, &two, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"489493","messageId":"35545c4b777efc3f4ede2ea2dbe3934eaaec34a5.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 08/11] repo_get_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:14Z","receivedAt":"2024-02-27T13:28:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `repo_get_merge_bases()` macro) is aware of that, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext step: adjust the callers of `get_octopus_merge_bases()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/log.c                    | 10 +++++-----\n builtin/merge-tree.c             |  5 +++--\n builtin/merge.c                  | 20 ++++++++++++--------\n builtin/rebase.c                 |  8 +++++---\n builtin/rev-parse.c              |  5 +++--\n commit-reach.c                   | 23 +++++++++++------------\n commit-reach.h                   |  7 ++++---\n diff-lib.c                       |  5 +++--\n log-tree.c                       |  5 +++--\n merge-ort.c                      |  6 +++++-\n merge-recursive.c                |  4 +++-\n notes-merge.c                    |  3 ++-\n object-name.c                    |  7 +++++--\n revision.c                       | 12 ++++++++----\n sequencer.c                      |  8 ++++++--\n submodule.c                      |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 12 ++++++++++++\n 17 files changed, 96 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 1705da71aca..befafd6ae04 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1702,11 +1702,11 @@ static struct commit *get_base_commit(const char *base_commit,\n \t */\n \twhile (rev_nr > 1) {\n \t\tfor (i = 0; i < rev_nr / 2; i++) {\n-\t\t\tstruct commit_list *merge_base;\n-\t\t\tmerge_base = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  rev[2 * i],\n-\t\t\t\t\t\t\t  rev[2 * i + 1]);\n-\t\t\tif (!merge_base || merge_base->next) {\n+\t\t\tstruct commit_list *merge_base = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository,\n+\t\t\t\t\t\t rev[2 * i],\n+\t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n+\t\t\t    !merge_base || merge_base->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex a35e0452d66..76200250629 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -463,8 +463,9 @@ static int real_merge(struct merge_tree_options *o,\n \t\t * Get the merge bases, in reverse order; see comment above\n \t\t * merge_incore_recursive in merge-ort.h\n \t\t */\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, parent1,\n-\t\t\t\t\t\t   parent2);\n+\t\tif (repo_get_merge_bases(the_repository, parent1,\n+\t\t\t\t\t parent2, &merge_bases) < 0)\n+\t\t\texit(128);\n \t\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d748d46e135..ac9d58adc29 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1517,10 +1517,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (!remoteheads)\n \t\t; /* already up-to-date */\n-\telse if (!remoteheads->next)\n-\t\tcommon = repo_get_merge_bases(the_repository, head_commit,\n-\t\t\t\t\t      remoteheads->item);\n-\telse {\n+\telse if (!remoteheads->next) {\n+\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t remoteheads->item, &common) < 0) {\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n+\t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n \t\tcommon = get_octopus_merge_bases(list);\n@@ -1631,7 +1634,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tstruct commit_list *j;\n \n \t\tfor (j = remoteheads; j; j = j->next) {\n-\t\t\tstruct commit_list *common_one;\n+\t\t\tstruct commit_list *common_one = NULL;\n \t\t\tstruct commit *common_item;\n \n \t\t\t/*\n@@ -1639,9 +1642,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t * merge_bases again, otherwise \"git merge HEAD^\n \t\t\t * HEAD^^\" would be missed.\n \t\t\t */\n-\t\t\tcommon_one = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  head_commit,\n-\t\t\t\t\t\t\t  j->item);\n+\t\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t\t j->item, &common_one) < 0)\n+\t\t\t\texit(128);\n+\n \t\t\tcommon_item = common_one->item;\n \t\t\tfree_commit_list(common_one);\n \t\t\tif (!oideq(&common_item->object.oid, &j->item->object.oid)) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 043c65dccd9..06a55fc7325 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -879,7 +879,8 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!upstream)\n \t\tgoto done;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, upstream, head);\n+\tif (repo_get_merge_bases(the_repository, upstream, head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\tgoto done;\n \n@@ -898,8 +899,9 @@ static void fill_branch_base(struct rebase_options *options,\n {\n \tstruct commit_list *merge_bases = NULL;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, options->onto,\n-\t\t\t\t\t   options->orig_head);\n+\tif (repo_get_merge_bases(the_repository, options->onto,\n+\t\t\t\t options->orig_head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\toidcpy(branch_base, null_oid());\n \telse\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex fde8861ca4e..c97d0f6144c 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -297,7 +297,7 @@ static int try_difference(const char *arg)\n \t\tshow_rev(NORMAL, &end_oid, end);\n \t\tshow_rev(symmetric ? NORMAL : REVERSED, &start_oid, start);\n \t\tif (symmetric) {\n-\t\t\tstruct commit_list *exclude;\n+\t\t\tstruct commit_list *exclude = NULL;\n \t\t\tstruct commit *a, *b;\n \t\t\ta = lookup_commit_reference(the_repository, &start_oid);\n \t\t\tb = lookup_commit_reference(the_repository, &end_oid);\n@@ -305,7 +305,8 @@ static int try_difference(const char *arg)\n \t\t\t\t*dotdot = '.';\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n+\t\t\t\texit(128);\n \t\t\twhile (exclude) {\n \t\t\t\tstruct commit *commit = pop_commit(&exclude);\n \t\t\t\tshow_rev(REVERSED, &commit->object.oid, NULL);\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5fa0abc7d1e..10e625ff51b 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -189,9 +189,12 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n \t\tfor (j = ret; j; j = j->next) {\n-\t\t\tstruct commit_list *bases;\n-\t\t\tbases = repo_get_merge_bases(the_repository, i->item,\n-\t\t\t\t\t\t     j->item);\n+\t\t\tstruct commit_list *bases = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository, i->item,\n+\t\t\t\t\t\t j->item, &bases) < 0) {\n+\t\t\t\tfree_commit_list(bases);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n \t\t\telse\n@@ -483,16 +486,12 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \treturn result;\n }\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *one,\n-\t\t\t\t\t struct commit *two)\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *one,\n+\t\t\t struct commit *two,\n+\t\t\t struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, 1, &two, 1, result);\n }\n \n /*\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 68f81549a44..2c6fcdd34f6 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -9,9 +9,10 @@ struct ref_filter;\n struct object_id;\n struct object_array;\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *rev1,\n-\t\t\t\t\t struct commit *rev2);\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *rev1,\n+\t\t\t struct commit *rev2,\n+\t\t\t struct commit_list **result);\n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      struct commit *one, int n,\n \t\t\t\t\t      struct commit **twos);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 0e9ec4f68af..498224ccce2 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -565,7 +565,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n {\n \tint i;\n \tstruct commit *mb_child[2] = {0};\n-\tstruct commit_list *merge_bases;\n+\tstruct commit_list *merge_bases = NULL;\n \n \tfor (i = 0; i < revs->pending.nr; i++) {\n \t\tstruct object *obj = revs->pending.objects[i].item;\n@@ -592,7 +592,8 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n \t\tmb_child[1] = lookup_commit_reference(the_repository, &oid);\n \t}\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]);\n+\tif (repo_get_merge_bases(the_repository, mb_child[0], mb_child[1], &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases)\n \t\tdie(_(\"no merge base found\"));\n \tif (merge_bases->next)\ndiff --git a/log-tree.c b/log-tree.c\nindex 504da6b519e..4f337766a39 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1010,7 +1010,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \t\t\t   struct object_id *oid)\n {\n \tstruct merge_options o;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct merge_result res = {0};\n \tstruct pretty_print_context ctx = {0};\n \tstruct commit *parent1 = parents->item;\n@@ -1035,7 +1035,8 @@ static int do_remerge_diff(struct rev_info *opt,\n \t/* Parse the relevant commits and get the merge bases */\n \tparse_commit_or_die(parent1);\n \tparse_commit_or_die(parent2);\n-\tbases = repo_get_merge_bases(the_repository, parent1, parent2);\n+\tif (repo_get_merge_bases(the_repository, parent1, parent2, &bases) < 0)\n+\t\texit(128);\n \n \t/* Re-merge the parents */\n \tmerge_incore_recursive(&o, bases, parent1, parent2, &res);\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9f3af46333a..90d8495ca1f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5066,7 +5066,11 @@ static void merge_ort_internal(struct merge_options *opt,\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tresult->clean = -1;\n+\t\t\treturn;\n+\t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0d931cc14ad..d609373d960 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3638,7 +3638,9 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0)\n+\t\t\treturn -1;\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 8799b522a55..51282934ae6 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -607,7 +607,8 @@ int notes_merge(struct notes_merge_options *o,\n \tassert(local && remote);\n \n \t/* Find merge bases */\n-\tbases = repo_get_merge_bases(the_repository, local, remote);\n+\tif (repo_get_merge_bases(the_repository, local, remote, &bases) < 0)\n+\t\texit(128);\n \tif (!bases) {\n \t\tbase_oid = null_oid();\n \t\tbase_tree_oid = the_hash_algo->empty_tree;\ndiff --git a/object-name.c b/object-name.c\nindex 0bfa29dbbfe..63bec6d9a2b 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1481,7 +1481,7 @@ int repo_get_oid_mb(struct repository *r,\n \t\t    struct object_id *oid)\n {\n \tstruct commit *one, *two;\n-\tstruct commit_list *mbs;\n+\tstruct commit_list *mbs = NULL;\n \tstruct object_id oid_tmp;\n \tconst char *dots;\n \tint st;\n@@ -1509,7 +1509,10 @@ int repo_get_oid_mb(struct repository *r,\n \ttwo = lookup_commit_reference_gently(r, &oid_tmp, 0);\n \tif (!two)\n \t\treturn -1;\n-\tmbs = repo_get_merge_bases(r, one, two);\n+\tif (repo_get_merge_bases(r, one, two, &mbs) < 0) {\n+\t\tfree_commit_list(mbs);\n+\t\treturn -1;\n+\t}\n \tif (!mbs || mbs->next)\n \t\tst = -1;\n \telse {\ndiff --git a/revision.c b/revision.c\nindex 00d5c29bfce..eb0d550842f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1965,7 +1965,7 @@ static void add_pending_commit_list(struct rev_info *revs,\n \n static void prepare_show_merge(struct rev_info *revs)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct commit *head, *other;\n \tstruct object_id oid;\n \tconst char **prune = NULL;\n@@ -1980,7 +1980,8 @@ static void prepare_show_merge(struct rev_info *revs)\n \tother = lookup_commit_or_die(&oid, \"MERGE_HEAD\");\n \tadd_pending_object(revs, &head->object, \"HEAD\");\n \tadd_pending_object(revs, &other->object, \"MERGE_HEAD\");\n-\tbases = repo_get_merge_bases(the_repository, head, other);\n+\tif (repo_get_merge_bases(the_repository, head, other, &bases) < 0)\n+\t\texit(128);\n \tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);\n \tadd_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);\n \tfree_commit_list(bases);\n@@ -2068,14 +2069,17 @@ static int handle_dotdot_1(const char *arg, char *dotdot,\n \t} else {\n \t\t/* A...B -- find merge bases between the two */\n \t\tstruct commit *a, *b;\n-\t\tstruct commit_list *exclude;\n+\t\tstruct commit_list *exclude = NULL;\n \n \t\ta = lookup_commit_reference(revs->repo, &a_obj->oid);\n \t\tb = lookup_commit_reference(revs->repo, &b_obj->oid);\n \t\tif (!a || !b)\n \t\t\treturn dotdot_missing(arg, dotdot, revs, symmetric);\n \n-\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) {\n+\t\t\tfree_commit_list(exclude);\n+\t\t\treturn -1;\n+\t\t}\n \t\tadd_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,\n \t\t\t\t     flags_exclude);\n \t\tadd_pending_commit_list(revs, exclude, flags_exclude);\ndiff --git a/sequencer.c b/sequencer.c\nindex d584cac8ed9..4417f2f1956 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3913,7 +3913,7 @@ static int do_merge(struct repository *r,\n \tint run_commit_flags = 0;\n \tstruct strbuf ref_name = STRBUF_INIT;\n \tstruct commit *head_commit, *merge_commit, *i;\n-\tstruct commit_list *bases, *j;\n+\tstruct commit_list *bases = NULL, *j;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts.nr &&\n \t\t(!opts->strategy ||\n@@ -4139,7 +4139,11 @@ static int do_merge(struct repository *r,\n \t}\n \n \tmerge_commit = to_merge->item;\n-\tbases = repo_get_merge_bases(r, head_commit, merge_commit);\n+\tif (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n \tif (bases && oideq(&merge_commit->object.oid,\n \t\t\t   &bases->item->object.oid)) {\n \t\tret = 0;\ndiff --git a/submodule.c b/submodule.c\nindex e603a19a876..04931a5474b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -595,7 +595,12 @@ static void show_submodule_header(struct diff_options *o,\n \t     (!is_null_oid(two) && !*right))\n \t\tmessage = \"(commits not present)\";\n \n-\t*merge_bases = repo_get_merge_bases(sub, *left, *right);\n+\t*merge_bases = NULL;\n+\tif (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) {\n+\t\tmessage = \"(corrupt repository)\";\n+\t\tgoto output_header;\n+\t}\n+\n \tif (*merge_bases) {\n \t\tif ((*merge_bases)->item == *left)\n \t\t\tfast_forward = 1;\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex b2c8a43fce3..5d1e7aca4c8 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -945,4 +945,16 @@ test_expect_success 'check the input format when --stdin is passed' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out on missing commits as well' '\n+\tgit init --bare missing-commit.git &&\n+\tgit rev-list --objects side1 side3 >list-including-initial &&\n+\tgrep -v ^$(git rev-parse side1^) <list-including-initial >list &&\n+\tgit pack-objects missing-commit.git/objects/pack/missing-initial <list &&\n+\tside1=$(git rev-parse side1) &&\n+\tside3=$(git rev-parse side3) &&\n+\ttest_must_fail git --git-dir=missing-commit.git \\\n+\t\tmerge-tree --allow-unrelated-histories $side1 $side3 >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"489494","messageId":"a963058d2ba51bc205d247e9e93c9941377aa4ae.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 09/11] get_octopus_merge_bases(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:15Z","receivedAt":"2024-02-27T13:28:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `get_merge_bases()` macro) is aware of that, too.\n\nNaturally, the callers need to be adjusted now, too.\n\nNext step: adjust `repo_get_merge_bases_many()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  8 ++++++--\n builtin/merge.c      |  6 +++++-\n builtin/pull.c       |  5 +++--\n commit-reach.c       | 20 +++++++++++---------\n commit-reach.h       |  2 +-\n 5 files changed, 26 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 0308fd73289..2edffc5487e 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -77,13 +77,17 @@ static int handle_independent(int count, const char **args)\n static int handle_octopus(int count, const char **args, int show_all)\n {\n \tstruct commit_list *revs = NULL;\n-\tstruct commit_list *result, *rev;\n+\tstruct commit_list *result = NULL, *rev;\n \tint i;\n \n \tfor (i = count - 1; i >= 0; i--)\n \t\tcommit_list_insert(get_commit_reference(args[i]), &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0) {\n+\t\tfree_commit_list(revs);\n+\t\tfree_commit_list(result);\n+\t\treturn 128;\n+\t}\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ac9d58adc29..94c5b693972 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1526,7 +1526,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n-\t\tcommon = get_octopus_merge_bases(list);\n+\t\tif (get_octopus_merge_bases(list, &common) < 0) {\n+\t\t\tfree(list);\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n \t\tfree(list);\n \t}\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e6f2942c0c5..0c5a55f2f4d 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -820,7 +820,7 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tconst struct object_id *merge_head,\n \t\tconst struct object_id *fork_point)\n {\n-\tstruct commit_list *revs = NULL, *result;\n+\tstruct commit_list *revs = NULL, *result = NULL;\n \n \tcommit_list_insert(lookup_commit_reference(the_repository, curr_head),\n \t\t\t   &revs);\n@@ -830,7 +830,8 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tcommit_list_insert(lookup_commit_reference(the_repository, fork_point),\n \t\t\t\t   &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0)\n+\t\texit(128);\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/commit-reach.c b/commit-reach.c\nindex 10e625ff51b..fa21a8f2f6b 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -176,24 +176,26 @@ static int merge_bases_many(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)\n {\n-\tstruct commit_list *i, *j, *k, *ret = NULL;\n+\tstruct commit_list *i, *j, *k;\n \n \tif (!in)\n-\t\treturn ret;\n+\t\treturn 0;\n \n-\tcommit_list_insert(in->item, &ret);\n+\tcommit_list_insert(in->item, result);\n \n \tfor (i = in->next; i; i = i->next) {\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n-\t\tfor (j = ret; j; j = j->next) {\n+\t\tfor (j = *result; j; j = j->next) {\n \t\t\tstruct commit_list *bases = NULL;\n \t\t\tif (repo_get_merge_bases(the_repository, i->item,\n \t\t\t\t\t\t j->item, &bases) < 0) {\n \t\t\t\tfree_commit_list(bases);\n-\t\t\t\treturn NULL;\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n+\t\t\t\treturn -1;\n \t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n@@ -202,10 +204,10 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\t\tfor (k = bases; k; k = k->next)\n \t\t\t\tend = k;\n \t\t}\n-\t\tfree_commit_list(ret);\n-\t\tret = new_commits;\n+\t\tfree_commit_list(*result);\n+\t\t*result = new_commits;\n \t}\n-\treturn ret;\n+\treturn 0;\n }\n \n static int remove_redundant_no_gen(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 2c6fcdd34f6..4690b6ecd0c 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -21,7 +21,7 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\n \t\t\t\t\t\t    struct commit **twos);\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in);\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n int repo_is_descendant_of(struct repository *r,\n \t\t\t  struct commit *commit,\n-- \ngitgitgadget\n\n"},{"id":"489495","messageId":"c3bed9c8500a0468d8c6a0d444b9e67dbae9e0b2.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 10/11] repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:16Z","receivedAt":"2024-02-27T13:28:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many()` function is aware of\nthat, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext stop: `repo_get_merge_bases_dirty()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n bisect.c              |  7 ++++---\n builtin/log.c         | 13 +++++++------\n commit-reach.c        | 16 ++++++----------\n commit-reach.h        |  7 ++++---\n commit.c              |  7 ++++---\n t/helper/test-reach.c |  9 ++++++---\n 6 files changed, 31 insertions(+), 28 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 1be8e0a2711..2018466d69f 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -851,10 +851,11 @@ static void handle_skipped_merge_base(const struct object_id *mb)\n static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)\n {\n \tenum bisect_error res = BISECT_OK;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \n-\tresult = repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n-\t\t\t\t\t   rev + 1);\n+\tif (repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n+\t\t\t\t      rev + 1, &result) < 0)\n+\t\texit(128);\n \n \tfor (; result; result = result->next) {\n \t\tconst struct object_id *mb = &result->item->object.oid;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex befafd6ae04..c75790a7cec 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1656,7 +1656,7 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\tstruct branch *curr_branch = branch_get(NULL);\n \t\tconst char *upstream = branch_get_upstream(curr_branch, NULL);\n \t\tif (upstream) {\n-\t\t\tstruct commit_list *base_list;\n+\t\t\tstruct commit_list *base_list = NULL;\n \t\t\tstruct commit *commit;\n \t\t\tstruct object_id oid;\n \n@@ -1667,11 +1667,12 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tcommit = lookup_commit_or_die(&oid, \"upstream base\");\n-\t\t\tbase_list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t      commit, total,\n-\t\t\t\t\t\t\t      list);\n-\t\t\t/* There should be one and only one merge base. */\n-\t\t\tif (!base_list || base_list->next) {\n+\t\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t\t      commit, total,\n+\t\t\t\t\t\t      list,\n+\t\t\t\t\t\t      &base_list) < 0 ||\n+\t\t\t    /* There should be one and only one merge base. */\n+\t\t\t    !base_list || base_list->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"could not find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/commit-reach.c b/commit-reach.c\nindex fa21a8f2f6b..954a05399f1 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -462,17 +462,13 @@ static int get_merge_bases_many_0(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one,\n-\t\t\t\t\t      int n,\n-\t\t\t\t\t      struct commit **twos)\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one,\n+\t\t\t      int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 4690b6ecd0c..458043f4d58 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -13,9 +13,10 @@ int repo_get_merge_bases(struct repository *r,\n \t\t\t struct commit *rev1,\n \t\t\t struct commit *rev2,\n \t\t\t struct commit_list **result);\n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one, int n,\n-\t\t\t\t\t      struct commit **twos);\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one, int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\ndiff --git a/commit.c b/commit.c\nindex 8405d7c3fce..00add5d81c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1054,7 +1054,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n {\n \tstruct object_id oid;\n \tstruct rev_collect revs;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint i;\n \tstruct commit *ret = NULL;\n \tchar *full_refname;\n@@ -1079,8 +1079,9 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n \tfor (i = 0; i < revs.nr; i++)\n \t\trevs.commit[i]->object.flags &= ~TMP_MARK;\n \n-\tbases = repo_get_merge_bases_many(the_repository, commit, revs.nr,\n-\t\t\t\t\t  revs.commit);\n+\tif (repo_get_merge_bases_many(the_repository, commit, revs.nr,\n+\t\t\t\t      revs.commit, &bases) < 0)\n+\t\texit(128);\n \n \t/*\n \t * There should be one and only one merge base, when we found\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex aa816e168ea..84ee9da8681 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -117,9 +117,12 @@ int cmd__reach(int ac, const char **av)\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-\t\tstruct commit_list *list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t\t     A, X_nr,\n-\t\t\t\t\t\t\t\t     X_array);\n+\t\tstruct commit_list *list = NULL;\n+\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t      A, X_nr,\n+\t\t\t\t\t      X_array,\n+\t\t\t\t\t      &list) < 0)\n+\t\t\texit(128);\n \t\tprintf(\"%s(A,X):\\n\", av[1]);\n \t\tprint_sorted_commit_ids(list);\n \t} else if (!strcmp(av[1], \"reduce_heads\")) {\n-- \ngitgitgadget\n\n"},{"id":"489496","messageId":"bdbf47ae505400b34fb41ab977d08938bb644fd0.1709040499.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v3 11/11] repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-27T13:28:17Z","receivedAt":"2024-02-27T13:28:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many_dirty()` function is\naware of that, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  9 ++++++---\n commit-reach.c       | 16 ++++++----------\n commit-reach.h       |  7 ++++---\n 3 files changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 2edffc5487e..a8a1ca53968 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -13,10 +13,13 @@\n \n static int show_merge_base(struct commit **rev, int rev_nr, int show_all)\n {\n-\tstruct commit_list *result, *r;\n+\tstruct commit_list *result = NULL, *r;\n \n-\tresult = repo_get_merge_bases_many_dirty(the_repository, rev[0],\n-\t\t\t\t\t\t rev_nr - 1, rev + 1);\n+\tif (repo_get_merge_bases_many_dirty(the_repository, rev[0],\n+\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn -1;\n+\t}\n \n \tif (!result)\n \t\treturn 1;\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 954a05399f1..2c69cb83d6f 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -471,17 +471,13 @@ int repo_get_merge_bases_many(struct repository *r,\n \treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one,\n-\t\t\t\t\t\t    int n,\n-\t\t\t\t\t\t    struct commit **twos)\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one,\n+\t\t\t\t    int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 0, result);\n }\n \n int repo_get_merge_bases(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 458043f4d58..bf63cc468fd 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -18,9 +18,10 @@ int repo_get_merge_bases_many(struct repository *r,\n \t\t\t      struct commit **twos,\n \t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t\t    struct commit **twos);\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one, int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result);\n \n int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n-- \ngitgitgadget\n"},{"id":"489516","messageId":"gha5nmnex4.fsf@gouders.net","threadId":"60905","inReplyTo":"84e7fbc07e08956e6c493baf499fee455887b16c.1709040499.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Dirk Gouders","fromEmail":"dirk@gouders.net","sentAt":"2024-02-27T14:46:15Z","receivedAt":"2024-02-27T14:46:33Z","isPatch":true,"sender":{"key":"dirk@gouders.net","avatar":"https://avatars.githubusercontent.com/u/81326422?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Currently, that logic pretends that a missing parent commit is\n                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n> equivalent to a missing parent commit, and for the purpose of\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                  \n> `--update-shallow` that is exactly what we need it to do.\n\nChances are that I am wrong, but to me the above sounds very irritating.\n\n> Therefore, let's introduce a flag to indicate when that is precisely the\n> logic we want.\n>\n> We need a flag, and cannot rely on `is_repository_shallow()` to indicate\n> that situation, because that function would return 0: There may not\n> actually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\n\nAgain, I'm not a native speaker but I understand the above as\n\"There may not even be an existing `shallow` file...\".\n\nI'm not sure -- the sentence just \"feels\" uncomfortable to me.\n\nDirk\n"},{"id":"489517","messageId":"gh34tenefb.fsf@gouders.net","threadId":"60905","inReplyTo":"85332b58c37717b5b8b6c826a2a3388dce3b0daa.1709040499.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 05/11] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Dirk Gouders","fromEmail":"dirk@gouders.net","sentAt":"2024-02-27T14:56:56Z","receivedAt":"2024-02-27T14:57:06Z","isPatch":true,"sender":{"key":"dirk@gouders.net","avatar":"https://avatars.githubusercontent.com/u/81326422?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Let's start at the bottom of the stack by teaching the\n> `paint_down_to_common()` function to return an `int`: if negative, it\n> indicates fatal error, if 0 success.\n\nKind of pedantic but the above doesn't describe the real change, i.e. a\nvalue != 0 indicates a fatal error:\n\n> -\t\tcommon = paint_down_to_common(r, array[i], filled,\n> -\t\t\t\t\t      work, min_generation, 0);\n> +\t\tif (paint_down_to_common(r, array[i], filled,\n> +\t\t\t\t\t work, min_generation, 0, &common)) {\n> +\t\t\tclear_commit_marks(array[i], all_flags);\n> +\t\t\tclear_commit_marks_many(filled, work, all_flags);\n> +\t\t\tfree_commit_list(common);\n> +\t\t\tfree(work);\n> +\t\t\tfree(redundant);\n> +\t\t\tfree(filled_index);\n> +\t\t\treturn -1;\n> +\t\t}\n\nDirk\n"},{"id":"489518","messageId":"79914d16-f58d-7ab0-5c25-f29870a73402@gmx.de","threadId":"60905","inReplyTo":"gha5nmnex4.fsf@gouders.net","subject":"Re: [PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-02-27T15:00:46Z","receivedAt":"2024-02-27T15:01:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Dirk,\n\nOn Tue, 27 Feb 2024, Dirk Gouders wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Currently, that logic pretends that a missing parent commit is\n>                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n> > equivalent to a missing parent commit, and for the purpose of\n>   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n> > `--update-shallow` that is exactly what we need it to do.\n>\n> Chances are that I am wrong, but to me the above sounds very irritating.\n\nNot only that, it's also wrong 😜\n\n> > Therefore, let's introduce a flag to indicate when that is precisely the\n> > logic we want.\n> >\n> > We need a flag, and cannot rely on `is_repository_shallow()` to indicate\n> > that situation, because that function would return 0: There may not\n> > actually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\n>\n> Again, I'm not a native speaker but I understand the above as\n> \"There may not even be an existing `shallow` file...\".\n\nI'm not a native speaker either, but I'll give it a try anyway. How about\nthis?\n\n\tCurrently, that logic pretends that a commit whose parent\n\tcommit is missing is a root commit (and likewise merge commits\n\twith missing parent commits are handled incorrectly, too).\n\n\tHowever, for the purpose of `--update-shallow` that is exactly\n\twhat we need to do (and only then).\n\n\tTherefore [...]\n\nBetter?\n\nCiao,\nJohannes\n"},{"id":"489533","messageId":"fa8ebbfd-e01f-fbde-c851-54c162b610ff@gmx.de","threadId":"60905","inReplyTo":"gh34tenefb.fsf@gouders.net","subject":"Re: [PATCH v3 05/11] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-02-27T15:08:16Z","receivedAt":"2024-02-27T15:08:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Dirk,\n\nOn Tue, 27 Feb 2024, Dirk Gouders wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Let's start at the bottom of the stack by teaching the\n> > `paint_down_to_common()` function to return an `int`: if negative, it\n> > indicates fatal error, if 0 success.\n>\n> Kind of pedantic but the above doesn't describe the real change, i.e. a\n> value != 0 indicates a fatal error:\n>\n> > -\t\tcommon = paint_down_to_common(r, array[i], filled,\n> > -\t\t\t\t\t      work, min_generation, 0);\n> > +\t\tif (paint_down_to_common(r, array[i], filled,\n> > +\t\t\t\t\t work, min_generation, 0, &common)) {\n\nThe fact that we do not bother to verify that the return value is\nnegative, but only check for a non-zero one instead, does not change the\nfact that in the form this patch leaves the code, `paint_down_to_common()`\nreturns -1 for fatal errors and 0 for success, as advertised, though.\n\nIs it lazy to omit the `< 0` here? Not actually, the reason why I omitted\nit here was to stay under 80 columns per line.\n\nGood eyes, though. If `paint_down_to_common()` _did_ return values other\nthan -1 and 0, in particular positive ones that would not indicate a fatal\nerror, the hunk under discussion would have introduced a problematic bug.\n\nCiao,\nJohannes\n\n> > +\t\t\tclear_commit_marks(array[i], all_flags);\n> > +\t\t\tclear_commit_marks_many(filled, work, all_flags);\n> > +\t\t\tfree_commit_list(common);\n> > +\t\t\tfree(work);\n> > +\t\t\tfree(redundant);\n> > +\t\t\tfree(filled_index);\n> > +\t\t\treturn -1;\n> > +\t\t}\n>\n> Dirk\n>\n>\n"},{"id":"489554","messageId":"xmqqcyshu6es.fsf@gitster.g","threadId":"60905","inReplyTo":"79914d16-f58d-7ab0-5c25-f29870a73402@gmx.de","subject":"Re: [PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-27T18:08:11Z","receivedAt":"2024-02-27T18:08:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> \tCurrently, that logic pretends that a commit whose parent\n> \tcommit is missing is a root commit (and likewise merge commits\n> \twith missing parent commits are handled incorrectly, too).\n> \tHowever, for the purpose of `--update-shallow` that is exactly\n> \twhat we need to do (and only then).\n\nI suspect that what made it harder to follow in the original\nconstruct is that we called the behaviour \"incorrect\" upfront and\nthen come back with \"that incorrectness is what we want\".  I wonder\nif it makes it easier to follow by flipping them around.\n\n    For the purpose of `--update-shallow`, when some of the parent\n    commits of a commit are missing from the repository, we need to\n    treat as if the parents of the commit are only the ones that do\n    exist in the repository and these missing commits have no\n    ancestry relationship with it.  If all its parent commits are\n    missing, the commit needs to be treated as if it were a root\n    commit.\n\n    Add a flag to optionally ask for such a behaviour, while\n    detecting missing objects as a repository corruption error by\n    default ...\n\nor something?\n\n> \tTherefore [...]\n>\n> Better?\n>\n> Ciao,\n> Johannes\n"},{"id":"489556","messageId":"xmqq8r35u6bf.fsf@gitster.g","threadId":"60905","inReplyTo":"xmqqcyshu6es.fsf@gitster.g","subject":"Re: [PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-27T18:10:12Z","receivedAt":"2024-02-27T18:10:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I suspect that what made it harder to follow in the original\n> construct is that we called the behaviour \"incorrect\" upfront and\n> then come back with \"that incorrectness is what we want\".\n\nOh, I forgot to say that lack of \"<area>: \" on the title of the\nearlier parts of the series were a bit uncomfortable to read.\n\n\n"},{"id":"489557","messageId":"xmqqwmqpsr2n.fsf@gitster.g","threadId":"60905","inReplyTo":"fa8ebbfd-e01f-fbde-c851-54c162b610ff@gmx.de","subject":"Re: [PATCH v3 05/11] commit-reach: start reporting errors in `paint_down_to_common()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-27T18:24:48Z","receivedAt":"2024-02-27T18:24:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Is it lazy to omit the `< 0` here? Not actually, the reason why I omitted\n> it here was to stay under 80 columns per line.\n>\n> Good eyes, though. If `paint_down_to_common()` _did_ return values other\n> than -1 and 0, in particular positive ones that would not indicate a fatal\n> error, the hunk under discussion would have introduced a problematic bug.\n\nThe same patch does compare the returned value with '< 0' in another\nfunction (that is far from this place), which probably made the hunk\nstand out during a review, I suspect.\n\nAfter having fixed a bug elsewhere about a codepath that mixed the\n\"non-zero is an error\" and \"negative is an error\" conventions during\nthe last cycle, I would have to say that being consistent would be\nnice.  I think we at the end decided to make the callee be more\nstrict (returning negative to signal an error) while allowing the\ncaller to be more lenient (taking non-zero as an error) in that\ncase.\n\nThanks.\n\n"},{"id":"489558","messageId":"xmqqr0gxsqvm.fsf@gitster.g","threadId":"60905","inReplyTo":"2ae6a54dd596c3192b5be32f0e4e2605f4aac6bd.1709040499.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 06/11] merge_bases_many(): pass on errors from `paint_down_to_common()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-27T18:29:01Z","receivedAt":"2024-02-27T18:29:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> The `paint_down_to_common()` function was just taught to indicate\n> parsing errors, and now the `merge_bases_many()` function is aware of\n> that, too.\n>\n> One tricky aspect is that `merge_bases_many()` parses commits of its\n> own, but wants to gracefully handle the scenario where NULL is passed as\n> a merge head, returning the empty list of merge bases. The way this was\n> handled involved calling `repo_parse_commit(NULL)` and relying on it to\n> return an error. This has to be done differently now so that we can\n> handle missing commits correctly by producing a fatal error.\n>\n> Next step: adjust the caller of `merge_bases_many()`:\n> `get_merge_bases_many_0()`.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  commit-reach.c | 35 ++++++++++++++++++++++-------------\n>  1 file changed, 22 insertions(+), 13 deletions(-)\n\nThis is the true gem of the series.  The next steps may have to be\nnoisy due to many callers adjusting to the new function signatures,\nbut split into these logical steps in a bottom-up way makes them\neasier to follow.  Nice.\n\n>\n> diff --git a/commit-reach.c b/commit-reach.c\n> index 9148a7dcbc0..2c74583c8e0 100644\n> --- a/commit-reach.c\n> +++ b/commit-reach.c\n> @@ -131,41 +131,49 @@ static int paint_down_to_common(struct repository *r,\n>  \treturn 0;\n>  }\n>  \n> -static struct commit_list *merge_bases_many(struct repository *r,\n> -\t\t\t\t\t    struct commit *one, int n,\n> -\t\t\t\t\t    struct commit **twos)\n> +static int merge_bases_many(struct repository *r,\n> +\t\t\t    struct commit *one, int n,\n> +\t\t\t    struct commit **twos,\n> +\t\t\t    struct commit_list **result)\n>  {\n>  \tstruct commit_list *list = NULL;\n> -\tstruct commit_list *result = NULL;\n>  \tint i;\n>  \n>  \tfor (i = 0; i < n; i++) {\n> -\t\tif (one == twos[i])\n> +\t\tif (one == twos[i]) {\n>  \t\t\t/*\n>  \t\t\t * We do not mark this even with RESULT so we do not\n>  \t\t\t * have to clean it up.\n>  \t\t\t */\n> -\t\t\treturn commit_list_insert(one, &result);\n> +\t\t\t*result = commit_list_insert(one, result);\n> +\t\t\treturn 0;\n> +\t\t}\n>  \t}\n>  \n> +\tif (!one)\n> +\t\treturn 0;\n>  \tif (repo_parse_commit(r, one))\n> -\t\treturn NULL;\n> +\t\treturn error(_(\"could not parse commit %s\"),\n> +\t\t\t     oid_to_hex(&one->object.oid));\n>  \tfor (i = 0; i < n; i++) {\n> +\t\tif (!twos[i])\n> +\t\t\treturn 0;\n>  \t\tif (repo_parse_commit(r, twos[i]))\n> -\t\t\treturn NULL;\n> +\t\t\treturn error(_(\"could not parse commit %s\"),\n> +\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n>  \t}\n>  \n>  \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n>  \t\tfree_commit_list(list);\n> -\t\treturn NULL;\n> +\t\treturn -1;\n>  \t}\n>  \n>  \twhile (list) {\n>  \t\tstruct commit *commit = pop_commit(&list);\n>  \t\tif (!(commit->object.flags & STALE))\n> -\t\t\tcommit_list_insert_by_date(commit, &result);\n> +\t\t\tcommit_list_insert_by_date(commit, result);\n>  \t}\n> -\treturn result;\n> +\treturn 0;\n>  }\n>  \n>  struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n> @@ -410,10 +418,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n>  {\n>  \tstruct commit_list *list;\n>  \tstruct commit **rslt;\n> -\tstruct commit_list *result;\n> +\tstruct commit_list *result = NULL;\n>  \tint cnt, i;\n>  \n> -\tresult = merge_bases_many(r, one, n, twos);\n> +\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n> +\t\treturn NULL;\n>  \tfor (i = 0; i < n; i++) {\n>  \t\tif (one == twos[i])\n>  \t\t\treturn result;\n"},{"id":"489567","messageId":"gha5nlbu9v.fsf@gouders.net","threadId":"60905","inReplyTo":"xmqqcyshu6es.fsf@gitster.g","subject":"Re: [PATCH v3 04/11] Prepare `paint_down_to_common()` for handling shallow commits","fromName":"Dirk Gouders","fromEmail":"dirk@gouders.net","sentAt":"2024-02-27T19:07:40Z","receivedAt":"2024-02-27T19:07:55Z","isPatch":true,"sender":{"key":"dirk@gouders.net","avatar":"https://avatars.githubusercontent.com/u/81326422?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n>> \tCurrently, that logic pretends that a commit whose parent\n>> \tcommit is missing is a root commit (and likewise merge commits\n>> \twith missing parent commits are handled incorrectly, too).\n>> \tHowever, for the purpose of `--update-shallow` that is exactly\n>> \twhat we need to do (and only then).\n>\n> I suspect that what made it harder to follow in the original\n> construct is that we called the behaviour \"incorrect\" upfront and\n> then come back with \"that incorrectness is what we want\".  I wonder\n> if it makes it easier to follow by flipping them around.\n>\n>     For the purpose of `--update-shallow`, when some of the parent\n>     commits of a commit are missing from the repository, we need to\n>     treat as if the parents of the commit are only the ones that do\n>     exist in the repository and these missing commits have no\n>     ancestry relationship with it.  If all its parent commits are\n>     missing, the commit needs to be treated as if it were a root\n>     commit.\n>\n>     Add a flag to optionally ask for such a behaviour, while\n>     detecting missing objects as a repository corruption error by\n>     default ...\n>\n> or something?\n\nAt least to me this is more understandable.\n\nDirk\n"},{"id":"489596","messageId":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v3.git.1709040497.gitgitgadget@gmail.com","subject":"[PATCH v4 00/11] The merge-base logic vs missing commit objects","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:06Z","receivedAt":"2024-02-28T09:44:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This patch series is in the same spirit as what I proposed in\nhttps://lore.kernel.org/git/pull.1651.git.1707212981.gitgitgadget@gmail.com/,\nwhere I tackled missing tree objects. This here patch series tackles missing\ncommit objects instead: Git's merge-base logic handles these missing commit\nobjects as if there had not been any commit object at all, i.e. if two\ncommits' merge base commit is missing, they will be treated as if they had\nno common commit history at all, which is a bug. Those commit objects should\nnot be missing, of course, i.e. this is only a problem in corrupt\nrepositories.\n\nThis patch series is a bit more tricky than the \"missing tree objects\" one,\nthough: The function signature of quite a few functions need to be changed\nto allow callers to discern between \"missing commit object\" vs \"no\nmerge-base found\".\n\nAnd of course it gets even more tricky because in shallow and partial clones\nwe expect commit objects to be missing, and that must not be treated like an\nerror but the existing logic is actually desirable in those scenarios.\n\nI am deeply sorry both about the length of this patch series as well as\nhaving to lean so heavily on reviews on the Git mailing list.\n\nChanges since v3:\n\n * Reworded some hard-to-understand paragraphs in the commit message of\n   \"Prepare paint_down_to_common() for handling shallow commits\" (4/11).\n * Changed an inconsistent paint_down_to_common() < 0 to simply test whether\n   the return value is non-zero.\n * Changed all commit messages to have proper <area>: prefixes.\n\nChanges since v2:\n\n * Moved a hunk from 3/11 to 2/11 that lets the repo_in_merge_bases_many()\n   function return an error if non-existing commits have been passed to it,\n   unless the ignore_missing_commits parameter is non-zero.\n * The end result is tree-same.\n\nChanges since v1:\n\n * Addressed a lot of memory leaks.\n * Reordered patch 2 and 3 to render the commit message's comment about\n   unchanged behavior true.\n * Fixed the incorrectly converted condition in the merge_submodule()\n   function.\n * The last patch (\"paint_down_to_common(): special-case shallow/partial\n   clones\") was dropped because it is not actually necessary, and the\n   explanation for that was added to the commit message of \"Prepare\n   paint_down_to_common() for handling shallow commits\".\n * An inconsistently-named variable i was renamed to be consistent with the\n   other variables called ret.\n\nJohannes Schindelin (11):\n  commit-reach(paint_down_to_common): plug two memory leaks\n  commit-reach(repo_in_merge_bases_many): optionally expect missing\n    commits\n  commit-reach(repo_in_merge_bases_many): report missing commits\n  commit-reach(paint_down_to_common): prepare for handling shallow\n    commits\n  commit-reach(paint_down_to_common): start reporting errors\n  commit-reach(merge_bases_many): pass on \"missing commits\" errors\n  commit-reach(get_merge_bases_many_0): pass on \"missing commits\" errors\n  commit-reach(repo_get_merge_bases): pass on \"missing commits\" errors\n  commit-reach(get_octopus_merge_bases): pass on \"missing commits\"\n    errors\n  commit-reach(repo_get_merge_bases_many): pass on \"missing commits\"\n    errors\n  commit-reach(repo_get_merge_bases_many_dirty): pass on errors\n\n bisect.c                         |   7 +-\n builtin/branch.c                 |  12 +-\n builtin/fast-import.c            |   6 +-\n builtin/fetch.c                  |   2 +\n builtin/log.c                    |  30 +++--\n builtin/merge-base.c             |  23 +++-\n builtin/merge-tree.c             |   5 +-\n builtin/merge.c                  |  26 ++--\n builtin/pull.c                   |   9 +-\n builtin/rebase.c                 |   8 +-\n builtin/receive-pack.c           |   6 +-\n builtin/rev-parse.c              |   5 +-\n commit-reach.c                   | 209 ++++++++++++++++++++-----------\n commit-reach.h                   |  26 ++--\n commit.c                         |   7 +-\n diff-lib.c                       |   5 +-\n http-push.c                      |   5 +-\n log-tree.c                       |   5 +-\n merge-ort.c                      |  87 +++++++++++--\n merge-recursive.c                |  58 +++++++--\n notes-merge.c                    |   3 +-\n object-name.c                    |   7 +-\n remote.c                         |   2 +-\n revision.c                       |  12 +-\n sequencer.c                      |   8 +-\n shallow.c                        |  21 ++--\n submodule.c                      |   7 +-\n t/helper/test-reach.c            |  11 +-\n t/t4301-merge-tree-write-tree.sh |  12 ++\n 29 files changed, 441 insertions(+), 183 deletions(-)\n\n\nbase-commit: 564d0252ca632e0264ed670534a51d18a689ef5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1657%2Fdscho%2Fmerge-base-and-missing-objects-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1657/dscho/merge-base-and-missing-objects-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1657\n\nRange-diff vs v3:\n\n  1:  6e4e409cd43 !  1:  647fa2ed5c5 paint_down_to_common: plug two memory leaks\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    paint_down_to_common: plug two memory leaks\n     +    commit-reach(paint_down_to_common): plug two memory leaks\n      \n          When a commit is missing, we return early (currently pretending that no\n          merge basis could be found in that case). At that stage, it is possible\n  2:  f68d77c6123 !  2:  48e69bf7229 Prepare `repo_in_merge_bases_many()` to optionally expect missing commits\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    Prepare `repo_in_merge_bases_many()` to optionally expect missing commits\n     +    commit-reach(repo_in_merge_bases_many): optionally expect missing commits\n      \n          Currently this function treats unrelated commit histories the same way\n          as commit histories with missing commit objects.\n  3:  0aaa224b5db !  3:  1938b317a49 Start reporting missing commits in `repo_in_merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    Start reporting missing commits in `repo_in_merge_bases_many()`\n     +    commit-reach(repo_in_merge_bases_many): report missing commits\n      \n          Some functions in Git's source code follow the convention that returning\n          a negative value indicates a fatal error, e.g. repository corruption.\n  4:  84e7fbc07e0 !  4:  837aa5a89c6 Prepare `paint_down_to_common()` for handling shallow commits\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    Prepare `paint_down_to_common()` for handling shallow commits\n     +    commit-reach(paint_down_to_common): prepare for handling shallow commits\n      \n          When `git fetch --update-shallow` needs to test for commit ancestry, it\n          can naturally run into a missing object (e.g. if it is a parent of a\n     -    shallow commit). To accommodate, the merge base logic will need to be\n     -    able to handle this situation gracefully.\n     +    shallow commit). For the purpose of `--update-shallow`, this needs to be\n     +    treated as if the child commit did not even have that parent, i.e. the\n     +    commit history needs to be clamped.\n      \n     -    Currently, that logic pretends that a missing parent commit is\n     -    equivalent to a missing parent commit, and for the purpose of\n     -    `--update-shallow` that is exactly what we need it to do.\n     +    For all other scenarios, clamping the commit history is actually a bug,\n     +    as it would hide repository corruption (for an analysis regarding\n     +    shallow and partial clones, see the analysis further down).\n      \n     -    Therefore, let's introduce a flag to indicate when that is precisely the\n     -    logic we want.\n     +    Add a flag to optionally ask the function to ignore missing commits, as\n     +    `--update-shallow` needs it to, while detecting missing objects as a\n     +    repository corruption error by default.\n      \n     -    We need a flag, and cannot rely on `is_repository_shallow()` to indicate\n     -    that situation, because that function would return 0: There may not\n     -    actually be a `shallow` file, as demonstrated e.g. by t5537.10 (\"add new\n     -    shallow root with receive.updateshallow on\") and t5538.4 (\"add new\n     -    shallow root with receive.updateshallow on\").\n     +    This flag is needed, and cannot replaced by `is_repository_shallow()` to\n     +    indicate that situation, because that function would return 0 in the\n     +    `--update-shallow` scenario: There is not actually a `shallow` file in\n     +    that scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\n     +    with receive.updateshallow on\") and t5538.4 (\"add new shallow root with\n     +    receive.updateshallow on\").\n      \n          Note: shallow commits' parents are set to `NULL` internally already,\n          therefore there is no need to special-case shallow repositories here, as\n  5:  85332b58c37 !  5:  b978b5d233a commit-reach: start reporting errors in `paint_down_to_common()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    commit-reach: start reporting errors in `paint_down_to_common()`\n     +    commit-reach(paint_down_to_common): start reporting errors\n      \n          If a commit cannot be parsed, it is currently ignored when looking for\n          merge bases. That's undesirable as the operation can pretend success in\n     @@ commit-reach.c: static struct commit_list *merge_bases_many(struct repository *r\n       \t}\n       \n      -\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n     -+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n     ++\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list)) {\n      +\t\tfree_commit_list(list);\n      +\t\treturn NULL;\n      +\t}\n  6:  2ae6a54dd59 !  6:  0f1ce130ce6 merge_bases_many(): pass on errors from `paint_down_to_common()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    merge_bases_many(): pass on errors from `paint_down_to_common()`\n     +    commit-reach(merge_bases_many): pass on \"missing commits\" errors\n      \n          The `paint_down_to_common()` function was just taught to indicate\n          parsing errors, and now the `merge_bases_many()` function is aware of\n     @@ commit-reach.c: static int paint_down_to_common(struct repository *r,\n      +\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n       \t}\n       \n     - \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list) < 0) {\n     + \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list)) {\n       \t\tfree_commit_list(list);\n      -\t\treturn NULL;\n      +\t\treturn -1;\n  7:  4321795102d !  7:  133b69b6a62 get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    get_merge_bases_many_0(): pass on errors from `merge_bases_many()`\n     +    commit-reach(get_merge_bases_many_0): pass on \"missing commits\" errors\n      \n          The `merge_bases_many()` function was just taught to indicate\n          parsing errors, and now the `get_merge_bases_many_0()` function is aware\n  8:  35545c4b777 !  8:  bd52f258cd9 repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    repo_get_merge_bases(): pass on errors from `merge_bases_many()`\n     +    commit-reach(repo_get_merge_bases): pass on \"missing commits\" errors\n      \n          The `merge_bases_many()` function was just taught to indicate parsing\n          errors, and now the `repo_get_merge_bases()` function (which is also\n  9:  a963058d2ba !  9:  b7ef90a57f0 get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    get_octopus_merge_bases(): pass on errors from `merge_bases_many()`\n     +    commit-reach(get_octopus_merge_bases): pass on \"missing commits\" errors\n      \n          The `merge_bases_many()` function was just taught to indicate parsing\n          errors, and now the `repo_get_merge_bases()` function (which is also\n 10:  c3bed9c8500 ! 10:  32587b3caa7 repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    repo_get_merge_bases_many(): pass on errors from `merge_bases_many()`\n     +    commit-reach(repo_get_merge_bases_many): pass on \"missing commits\" errors\n      \n          The `merge_bases_many()` function was just taught to indicate parsing\n          errors, and now the `repo_get_merge_bases_many()` function is aware of\n 11:  bdbf47ae505 ! 11:  05de9f24444 repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    repo_get_merge_bases_many_dirty(): pass on errors from `merge_bases_many()`\n     +    commit-reach(repo_get_merge_bases_many_dirty): pass on errors\n     +\n     +    (Actually, this commit is only about passing on \"missing commits\"\n     +    errors, but adding that to the commit's title would have made it too\n     +    long.)\n      \n          The `merge_bases_many()` function was just taught to indicate parsing\n          errors, and now the `repo_get_merge_bases_many_dirty()` function is\n\n-- \ngitgitgadget\n"},{"id":"489597","messageId":"647fa2ed5c5418d2298672a239b7723912358cd7.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 01/11] commit-reach(paint_down_to_common): plug two memory leaks","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:07Z","receivedAt":"2024-02-28T09:44:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen a commit is missing, we return early (currently pretending that no\nmerge basis could be found in that case). At that stage, it is possible\nthat a merge base could have been found already, and added to the\n`result`, which is now leaked.\n\nThe priority queue has a similar issue: There might still be a commit in\nthat queue.\n\nLet's release both, to address the potential memory leaks.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex a868a575ea1..7ea916f9ebd 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -105,8 +105,11 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tparents = parents->next;\n \t\t\tif ((p->object.flags & flags) == flags)\n \t\t\t\tcontinue;\n-\t\t\tif (repo_parse_commit(r, p))\n+\t\t\tif (repo_parse_commit(r, p)) {\n+\t\t\t\tclear_prio_queue(&queue);\n+\t\t\t\tfree_commit_list(result);\n \t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n \t\t}\n-- \ngitgitgadget\n\n"},{"id":"489598","messageId":"48e69bf7229bdbf8c944cd8440a54a0ce003ab44.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 02/11] commit-reach(repo_in_merge_bases_many): optionally expect missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:08Z","receivedAt":"2024-02-28T09:44:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nCurrently this function treats unrelated commit histories the same way\nas commit histories with missing commit objects.\n\nTypically, missing commit objects constitute a corrupt repository,\nthough, and should be reported as such. The next commits will make it\nso, but there is one exception: In `git fetch --update-shallow` we\n_expect_ commit objects to be missing, and we do want to treat the\nnow-incomplete commit histories as unrelated.\n\nTo allow for that, let's introduce an additional parameter that is\npassed to `repo_in_merge_bases_many()` to trigger this behavior, and use\nit in the two callers in `shallow.c`.\n\nThis commit changes behavior slightly: unless called from the\n`shallow.c` functions that set the `ignore_missing_commits` bit, any\nnon-existing tip commit that is passed to `repo_in_merge_bases_many()`\nwill now result in an error.\n\nNote: When encountering missing commits while traversing the commit\nhistory in search for merge bases, with this commit there won't be a\nchange in behavior just yet, their children will still be interpreted as\nroot commits. This bug will get fixed by follow-up commits.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c        | 9 +++++----\n commit-reach.h        | 3 ++-\n remote.c              | 2 +-\n shallow.c             | 5 +++--\n t/helper/test-reach.c | 2 +-\n 5 files changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7ea916f9ebd..5c1b5256598 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -467,7 +467,7 @@ int repo_is_descendant_of(struct repository *r,\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit))\n+\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n \t\t\t\treturn 1;\n \t\t}\n \t\treturn 0;\n@@ -478,17 +478,18 @@ int repo_is_descendant_of(struct repository *r,\n  * Is \"commit\" an ancestor of one of the \"references\"?\n  */\n int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference)\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits)\n {\n \tstruct commit_list *bases;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n \tif (repo_parse_commit(r, commit))\n-\t\treturn ret;\n+\t\treturn ignore_missing_commits ? 0 : -1;\n \tfor (i = 0; i < nr_reference; i++) {\n \t\tif (repo_parse_commit(r, reference[i]))\n-\t\t\treturn ret;\n+\t\t\treturn ignore_missing_commits ? 0 : -1;\n \n \t\tgeneration = commit_graph_generation(reference[i]);\n \t\tif (generation > max_generation)\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 35c4da49481..68f81549a44 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -30,7 +30,8 @@ int repo_in_merge_bases(struct repository *r,\n \t\t\tstruct commit *reference);\n int repo_in_merge_bases_many(struct repository *r,\n \t\t\t     struct commit *commit,\n-\t\t\t     int nr_reference, struct commit **reference);\n+\t\t\t     int nr_reference, struct commit **reference,\n+\t\t\t     int ignore_missing_commits);\n \n /*\n  * Takes a list of commits and returns a new list where those\ndiff --git a/remote.c b/remote.c\nindex abb24822beb..763c80f4a7d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2675,7 +2675,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n \t\tif (MERGE_BASES_BATCH_SIZE < size)\n \t\t\tsize = MERGE_BASES_BATCH_SIZE;\n \n-\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk)))\n+\t\tif ((ret = repo_in_merge_bases_many(the_repository, commit, size, chunk, 0)))\n \t\t\tbreak;\n \t}\n \ndiff --git a/shallow.c b/shallow.c\nindex ac728cdd778..dfcc1f86a7f 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -797,7 +797,7 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tfor (j = 0; j < bitmap_nr; j++)\n \t\t\tif (bitmap[0][j] &&\n \t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits)) {\n+\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n \t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n \t\t\t\tdst++;\n \t\t\t\tbreak;\n@@ -828,7 +828,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\tsi->reachable[c] = repo_in_merge_bases_many(the_repository,\n \t\t\t\t\t\t\t    commit,\n \t\t\t\t\t\t\t    si->nr_commits,\n-\t\t\t\t\t\t\t    si->commits);\n+\t\t\t\t\t\t\t    si->commits,\n+\t\t\t\t\t\t\t    1);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 3e173399a00..aa816e168ea 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -113,7 +113,7 @@ int cmd__reach(int ac, const char **av)\n \t\t       repo_in_merge_bases(the_repository, A, B));\n \telse if (!strcmp(av[1], \"in_merge_bases_many\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1],\n-\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array));\n+\t\t       repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-- \ngitgitgadget\n\n"},{"id":"489600","messageId":"1938b317a49f4d688bfefd7e5a74ad750a55a91a.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 03/11] commit-reach(repo_in_merge_bases_many): report missing commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:09Z","receivedAt":"2024-02-28T09:44:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nSome functions in Git's source code follow the convention that returning\na negative value indicates a fatal error, e.g. repository corruption.\n\nLet's use this convention in `repo_in_merge_bases()` to report when one\nof the specified commits is missing (i.e. when `repo_parse_commit()`\nreports an error).\n\nAlso adjust the callers of `repo_in_merge_bases()` to handle such\nnegative return values.\n\nNote: As of this patch, errors are returned only if any of the specified\nmerge heads is missing. Over the course of the next patches, missing\ncommits will also be reported by the `paint_down_to_common()` function,\nwhich is called by `repo_in_merge_bases_many()`, and those errors will\nbe properly propagated back to the caller at that stage.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/branch.c       | 12 +++++--\n builtin/fast-import.c  |  6 +++-\n builtin/fetch.c        |  2 ++\n builtin/log.c          |  7 ++--\n builtin/merge-base.c   |  6 +++-\n builtin/pull.c         |  4 +++\n builtin/receive-pack.c |  6 +++-\n commit-reach.c         |  8 +++--\n http-push.c            |  5 ++-\n merge-ort.c            | 81 ++++++++++++++++++++++++++++++++++++------\n merge-recursive.c      | 54 +++++++++++++++++++++++-----\n shallow.c              | 18 ++++++----\n 12 files changed, 173 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e7ee9bd0f15..7f9e79237f3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -161,6 +161,8 @@ static int branch_merged(int kind, const char *name,\n \n \tmerged = reference_rev ? repo_in_merge_bases(the_repository, rev,\n \t\t\t\t\t\t     reference_rev) : 0;\n+\tif (merged < 0)\n+\t\texit(128);\n \n \t/*\n \t * After the safety valve is fully redefined to \"check with\n@@ -169,9 +171,13 @@ static int branch_merged(int kind, const char *name,\n \t * any of the following code, but during the transition period,\n \t * a gentle reminder is in order.\n \t */\n-\tif ((head_rev != reference_rev) &&\n-\t    (head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0) != merged) {\n-\t\tif (merged)\n+\tif (head_rev != reference_rev) {\n+\t\tint expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0;\n+\t\tif (expect < 0)\n+\t\t\texit(128);\n+\t\tif (expect == merged)\n+\t\t\t; /* okay */\n+\t\telse if (merged)\n \t\t\twarning(_(\"deleting branch '%s' that has been merged to\\n\"\n \t\t\t\t\"         '%s', but not yet merged to HEAD\"),\n \t\t\t\tname, reference_name);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 444f41cf8ca..14c2efa88fc 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1625,6 +1625,7 @@ static int update_branch(struct branch *b)\n \t\toidclr(&old_oid);\n \tif (!force_update && !is_null_oid(&old_oid)) {\n \t\tstruct commit *old_cmit, *new_cmit;\n+\t\tint ret;\n \n \t\told_cmit = lookup_commit_reference_gently(the_repository,\n \t\t\t\t\t\t\t  &old_oid, 0);\n@@ -1633,7 +1634,10 @@ static int update_branch(struct branch *b)\n \t\tif (!old_cmit || !new_cmit)\n \t\t\treturn error(\"Branch %s is missing commits.\", b->name);\n \n-\t\tif (!repo_in_merge_bases(the_repository, old_cmit, new_cmit)) {\n+\t\tret = repo_in_merge_bases(the_repository, old_cmit, new_cmit);\n+\t\tif (ret < 0)\n+\t\t\texit(128);\n+\t\tif (!ret) {\n \t\t\twarning(\"Not updating %s\"\n \t\t\t\t\" (new tip %s does not contain %s)\",\n \t\t\t\tb->name, oid_to_hex(&b->oid),\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex fd134ba74d9..0584a1f8b64 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -978,6 +978,8 @@ static int update_local_ref(struct ref *ref,\n \t\tuint64_t t_before = getnanotime();\n \t\tfast_forward = repo_in_merge_bases(the_repository, current,\n \t\t\t\t\t\t   updated);\n+\t\tif (fast_forward < 0)\n+\t\t\texit(128);\n \t\tforced_updates_ms += (getnanotime() - t_before) / 1000000;\n \t} else {\n \t\tfast_forward = 1;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ba775d7b5cf..1705da71aca 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1623,7 +1623,7 @@ static struct commit *get_base_commit(const char *base_commit,\n {\n \tstruct commit *base = NULL;\n \tstruct commit **rev;\n-\tint i = 0, rev_nr = 0, auto_select, die_on_failure;\n+\tint i = 0, rev_nr = 0, auto_select, die_on_failure, ret;\n \n \tswitch (auto_base) {\n \tcase AUTO_BASE_NEVER:\n@@ -1723,7 +1723,10 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\trev_nr = DIV_ROUND_UP(rev_nr, 2);\n \t}\n \n-\tif (!repo_in_merge_bases(the_repository, base, rev[0])) {\n+\tret = repo_in_merge_bases(the_repository, base, rev[0]);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (!ret) {\n \t\tif (die_on_failure) {\n \t\t\tdie(_(\"base commit should be the ancestor of revision list\"));\n \t\t} else {\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex e68b7fe45d7..0308fd73289 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -103,12 +103,16 @@ static int handle_octopus(int count, const char **args, int show_all)\n static int handle_is_ancestor(int argc, const char **argv)\n {\n \tstruct commit *one, *two;\n+\tint ret;\n \n \tif (argc != 2)\n \t\tdie(\"--is-ancestor takes exactly two commits\");\n \tone = get_commit_reference(argv[0]);\n \ttwo = get_commit_reference(argv[1]);\n-\tif (repo_in_merge_bases(the_repository, one, two))\n+\tret = repo_in_merge_bases(the_repository, one, two);\n+\tif (ret < 0)\n+\t\texit(128);\n+\tif (ret)\n \t\treturn 0;\n \telse\n \t\treturn 1;\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex be2b2c9ebc9..e6f2942c0c5 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -931,6 +931,8 @@ static int get_can_ff(struct object_id *orig_head,\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n \tret = repo_is_descendant_of(the_repository, merge_head, list);\n \tfree_commit_list(list);\n+\tif (ret < 0)\n+\t\texit(128);\n \treturn ret;\n }\n \n@@ -955,6 +957,8 @@ static int already_up_to_date(struct object_id *orig_head,\n \t\tcommit_list_insert(theirs, &list);\n \t\tok = repo_is_descendant_of(the_repository, ours, list);\n \t\tfree_commit_list(list);\n+\t\tif (ok < 0)\n+\t\t\texit(128);\n \t\tif (!ok)\n \t\t\treturn 0;\n \t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8c4f0cb90a9..956fea6293e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1546,6 +1546,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t    starts_with(name, \"refs/heads/\")) {\n \t\tstruct object *old_object, *new_object;\n \t\tstruct commit *old_commit, *new_commit;\n+\t\tint ret2;\n \n \t\told_object = parse_object(the_repository, old_oid);\n \t\tnew_object = parse_object(the_repository, new_oid);\n@@ -1559,7 +1560,10 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t}\n \t\told_commit = (struct commit *)old_object;\n \t\tnew_commit = (struct commit *)new_object;\n-\t\tif (!repo_in_merge_bases(the_repository, old_commit, new_commit)) {\n+\t\tret2 = repo_in_merge_bases(the_repository, old_commit, new_commit);\n+\t\tif (ret2 < 0)\n+\t\t\texit(128);\n+\t\tif (!ret2) {\n \t\t\trp_error(\"denying non-fast-forward %s\"\n \t\t\t\t \" (you should pull first)\", name);\n \t\t\tret = \"non-fast-forward\";\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5c1b5256598..5ff71d72d51 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -464,11 +464,13 @@ int repo_is_descendant_of(struct repository *r,\n \t} else {\n \t\twhile (with_commit) {\n \t\t\tstruct commit *other;\n+\t\t\tint ret;\n \n \t\t\tother = with_commit->item;\n \t\t\twith_commit = with_commit->next;\n-\t\t\tif (repo_in_merge_bases_many(r, other, 1, &commit, 0))\n-\t\t\t\treturn 1;\n+\t\t\tret = repo_in_merge_bases_many(r, other, 1, &commit, 0);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n \t\t}\n \t\treturn 0;\n \t}\n@@ -598,6 +600,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)\n \tcommit_list_insert(old_commit, &old_commit_list);\n \tret = repo_is_descendant_of(the_repository,\n \t\t\t\t    new_commit, old_commit_list);\n+\tif (ret < 0)\n+\t\texit(128);\n \tfree_commit_list(old_commit_list);\n \treturn ret;\n }\ndiff --git a/http-push.c b/http-push.c\nindex a704f490fdb..24c16a4f5ff 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1576,8 +1576,11 @@ static int verify_merge_base(struct object_id *head_oid, struct ref *remote)\n \tstruct commit *head = lookup_commit_or_die(head_oid, \"HEAD\");\n \tstruct commit *branch = lookup_commit_or_die(&remote->old_oid,\n \t\t\t\t\t\t     remote->name);\n+\tint ret = repo_in_merge_bases(the_repository, branch, head);\n \n-\treturn repo_in_merge_bases(the_repository, branch, head);\n+\tif (ret < 0)\n+\t\texit(128);\n+\treturn ret;\n }\n \n static int delete_remote_branch(const char *pattern, int force)\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 6491070d965..9f3af46333a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -544,6 +544,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n \tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n+\tCONFLICT_SUBMODULE_CORRUPT,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -596,7 +597,9 @@ static const char *type_short_descriptions[] = {\n \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n \t\t\"CONFLICT (submodule may have rewinds)\",\n \t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n-\t\t\"CONFLICT (submodule lacks merge base)\"\n+\t\t\"CONFLICT (submodule lacks merge base)\",\n+\t[CONFLICT_SUBMODULE_CORRUPT] =\n+\t\t\"CONFLICT (submodule corrupt)\"\n };\n \n struct logical_conflict_info {\n@@ -1710,7 +1713,14 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret > 0)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1725,9 +1735,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1749,7 +1767,7 @@ static int merge_submodule(struct merge_options *opt,\n {\n \tstruct repository subrepo;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1796,8 +1814,26 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_o, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s \"\n@@ -1807,7 +1843,16 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, b);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1816,7 +1861,16 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0) {\n \t\toidcpy(result, a);\n \t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n \t\t\t path, NULL, NULL, NULL,\n@@ -1841,6 +1895,13 @@ static int merge_submodule(struct merge_options *opt,\n \tparent_count = find_first_merges(&subrepo, path, commit_a, commit_b,\n \t\t\t\t\t &merges);\n \tswitch (parent_count) {\n+\tcase -1:\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_CORRUPT, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s \"\n+\t\t\t   \"(repository corrupt)\"),\n+\t\t\t path);\n+\t\tbreak;\n \tcase 0:\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n \t\t\t path, NULL, NULL, NULL,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3beb0801b1..0d931cc14ad 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1144,7 +1144,13 @@ static int find_first_merges(struct repository *repo,\n \t\tdie(\"revision walk setup failed\");\n \twhile ((commit = get_revision(&revs)) != NULL) {\n \t\tstruct object *o = &(commit->object);\n-\t\tif (repo_in_merge_bases(repo, b, commit))\n+\t\tint ret = repo_in_merge_bases(repo, b, commit);\n+\t\tif (ret < 0) {\n+\t\t\tobject_array_clear(&merges);\n+\t\t\trelease_revisions(&revs);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tif (ret)\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n \treset_revision_walk();\n@@ -1159,9 +1165,17 @@ static int find_first_merges(struct repository *repo,\n \t\tcontains_another = 0;\n \t\tfor (j = 0; j < merges.nr; j++) {\n \t\t\tstruct commit *m2 = (struct commit *) merges.objects[j].item;\n-\t\t\tif (i != j && repo_in_merge_bases(repo, m2, m1)) {\n-\t\t\t\tcontains_another = 1;\n-\t\t\t\tbreak;\n+\t\t\tif (i != j) {\n+\t\t\t\tint ret = repo_in_merge_bases(repo, m2, m1);\n+\t\t\t\tif (ret < 0) {\n+\t\t\t\t\tobject_array_clear(&merges);\n+\t\t\t\t\trelease_revisions(&revs);\n+\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tif (ret > 0) {\n+\t\t\t\t\tcontains_another = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n@@ -1197,7 +1211,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   const struct object_id *b)\n {\n \tstruct repository subrepo;\n-\tint ret = 0;\n+\tint ret = 0, ret2;\n \tstruct commit *commit_base, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n@@ -1234,14 +1248,29 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \n \t/* check whether both changes are forward */\n-\tif (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||\n-\t    !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2 > 0)\n+\t\tret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (!ret2) {\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (commits don't follow merge-base)\"), path);\n \t\tgoto cleanup;\n \t}\n \n \t/* Case #1: a is contained in b or vice versa */\n-\tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, b);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1254,7 +1283,12 @@ static int merge_submodule(struct merge_options *opt,\n \t\tret = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n+\tret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a);\n+\tif (ret2 < 0) {\n+\t\toutput(opt, 1, _(\"Failed to merge submodule %s (repository corrupt)\"), path);\n+\t\tgoto cleanup;\n+\t}\n+\tif (ret2) {\n \t\toidcpy(result, a);\n \t\tif (show(opt, 3)) {\n \t\t\toutput(opt, 3, _(\"Fast-forwarding submodule %s to the following commit:\"), path);\n@@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&o->oid,\n \t\t\t\t\t\t\t&a->oid,\n \t\t\t\t\t\t\t&b->oid);\n+\t\t\tif (result->clean < 0)\n+\t\t\t\treturn -1;\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n \t\t\tcase MERGE_VARIANT_NORMAL:\ndiff --git a/shallow.c b/shallow.c\nindex dfcc1f86a7f..f71496f35c3 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -795,12 +795,16 @@ static void post_assign_shallow(struct shallow_info *info,\n \t\tif (!*bitmap)\n \t\t\tcontinue;\n \t\tfor (j = 0; j < bitmap_nr; j++)\n-\t\t\tif (bitmap[0][j] &&\n-\t\t\t    /* Step 7, reachability test at commit level */\n-\t\t\t    !repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1)) {\n-\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n-\t\t\t\tdst++;\n-\t\t\t\tbreak;\n+\t\t\tif (bitmap[0][j]) {\n+\t\t\t\t/* Step 7, reachability test at commit level */\n+\t\t\t\tint ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\texit(128);\n+\t\t\t\tif (!ret) {\n+\t\t\t\t\tupdate_refstatus(ref_status, info->ref->nr, *bitmap);\n+\t\t\t\t\tdst++;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t}\n \t}\n \tinfo->nr_ours = dst;\n@@ -830,6 +834,8 @@ int delayed_reachability_test(struct shallow_info *si, int c)\n \t\t\t\t\t\t\t    si->nr_commits,\n \t\t\t\t\t\t\t    si->commits,\n \t\t\t\t\t\t\t    1);\n+\t\tif (si->reachable[c] < 0)\n+\t\t\texit(128);\n \t\tsi->need_reachability_test[c] = 0;\n \t}\n \treturn si->reachable[c];\n-- \ngitgitgadget\n\n"},{"id":"489599","messageId":"837aa5a89c640427a5de064da93f1de4934d8212.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:10Z","receivedAt":"2024-02-28T09:44:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWhen `git fetch --update-shallow` needs to test for commit ancestry, it\ncan naturally run into a missing object (e.g. if it is a parent of a\nshallow commit). For the purpose of `--update-shallow`, this needs to be\ntreated as if the child commit did not even have that parent, i.e. the\ncommit history needs to be clamped.\n\nFor all other scenarios, clamping the commit history is actually a bug,\nas it would hide repository corruption (for an analysis regarding\nshallow and partial clones, see the analysis further down).\n\nAdd a flag to optionally ask the function to ignore missing commits, as\n`--update-shallow` needs it to, while detecting missing objects as a\nrepository corruption error by default.\n\nThis flag is needed, and cannot replaced by `is_repository_shallow()` to\nindicate that situation, because that function would return 0 in the\n`--update-shallow` scenario: There is not actually a `shallow` file in\nthat scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\nwith receive.updateshallow on\") and t5538.4 (\"add new shallow root with\nreceive.updateshallow on\").\n\nNote: shallow commits' parents are set to `NULL` internally already,\ntherefore there is no need to special-case shallow repositories here, as\nthe merge-base logic will not try to access parent commits of shallow\ncommits.\n\nLikewise, partial clones aren't an issue either: If a commit is missing\nduring the revision walk in the merge-base logic, it is fetched via\n`promisor_remote_get_direct()`. And not only the single missing commit\nobject: Due to the way the \"promised\" objects are fetched (in\n`fetch_objects()` in `promisor-remote.c`, using `fetch\n--filter=blob:none`), there is no actual way to fetch a single commit\nobject, as the remote side will pass that commit OID to `pack-objects\n--revs [...]` which in turn passes it to `rev-list` which interprets\nthis as a commit _range_ instead of a single object. Therefore, in\npartial clones (unless they are shallow in addition), all commits\nreachable from a commit that is in the local object database are also\npresent in that local database.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 5ff71d72d51..7112b10eeea 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -53,7 +53,8 @@ static int queue_has_nonstale(struct prio_queue *queue)\n static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t\t\tstruct commit *one, int n,\n \t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation)\n+\t\t\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\t\t\tint ignore_missing_commits)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n \tstruct commit_list *result = NULL;\n@@ -108,6 +109,13 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n \t\t\t\tfree_commit_list(result);\n+\t\t\t\t/*\n+\t\t\t\t * At this stage, we know that the commit is\n+\t\t\t\t * missing: `repo_parse_commit()` uses\n+\t\t\t\t * `OBJECT_INFO_DIE_IF_CORRUPT` and therefore\n+\t\t\t\t * corrupt commits would already have been\n+\t\t\t\t * dispatched with a `die()`.\n+\t\t\t\t */\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n@@ -143,7 +151,7 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0);\n+\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -214,7 +222,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n \t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation);\n+\t\t\t\t\t      work, min_generation, 0);\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -504,7 +512,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \n \tbases = paint_down_to_common(r, commit,\n \t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation);\n+\t\t\t\t     generation, ignore_missing_commits);\n \tif (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n-- \ngitgitgadget\n\n"},{"id":"489601","messageId":"b978b5d233a3ccc4f498f9e7d48fae0ce651db49.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 05/11] commit-reach(paint_down_to_common): start reporting errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:11Z","receivedAt":"2024-02-28T09:44:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nIf a commit cannot be parsed, it is currently ignored when looking for\nmerge bases. That's undesirable as the operation can pretend success in\na corrupt repository, even though the command should fail with an error\nmessage.\n\nLet's start at the bottom of the stack by teaching the\n`paint_down_to_common()` function to return an `int`: if negative, it\nindicates fatal error, if 0 success.\n\nThis requires a couple of callers to be adjusted accordingly.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 66 ++++++++++++++++++++++++++++++++++----------------\n 1 file changed, 45 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 7112b10eeea..9ad5f9db4f7 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -50,14 +50,14 @@ static int queue_has_nonstale(struct prio_queue *queue)\n }\n \n /* all input commits in one and twos[] must have been parsed! */\n-static struct commit_list *paint_down_to_common(struct repository *r,\n-\t\t\t\t\t\tstruct commit *one, int n,\n-\t\t\t\t\t\tstruct commit **twos,\n-\t\t\t\t\t\ttimestamp_t min_generation,\n-\t\t\t\t\t\tint ignore_missing_commits)\n+static int paint_down_to_common(struct repository *r,\n+\t\t\t\tstruct commit *one, int n,\n+\t\t\t\tstruct commit **twos,\n+\t\t\t\ttimestamp_t min_generation,\n+\t\t\t\tint ignore_missing_commits,\n+\t\t\t\tstruct commit_list **result)\n {\n \tstruct prio_queue queue = { compare_commits_by_gen_then_commit_date };\n-\tstruct commit_list *result = NULL;\n \tint i;\n \ttimestamp_t last_gen = GENERATION_NUMBER_INFINITY;\n \n@@ -66,8 +66,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \n \tone->object.flags |= PARENT1;\n \tif (!n) {\n-\t\tcommit_list_append(one, &result);\n-\t\treturn result;\n+\t\tcommit_list_append(one, result);\n+\t\treturn 0;\n \t}\n \tprio_queue_put(&queue, one);\n \n@@ -95,7 +95,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\tif (flags == (PARENT1 | PARENT2)) {\n \t\t\tif (!(commit->object.flags & RESULT)) {\n \t\t\t\tcommit->object.flags |= RESULT;\n-\t\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\t\tcommit_list_insert_by_date(commit, result);\n \t\t\t}\n \t\t\t/* Mark parents of a found merge stale */\n \t\t\tflags |= STALE;\n@@ -108,7 +108,8 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\tcontinue;\n \t\t\tif (repo_parse_commit(r, p)) {\n \t\t\t\tclear_prio_queue(&queue);\n-\t\t\t\tfree_commit_list(result);\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n \t\t\t\t/*\n \t\t\t\t * At this stage, we know that the commit is\n \t\t\t\t * missing: `repo_parse_commit()` uses\n@@ -116,7 +117,10 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t\t\t\t * corrupt commits would already have been\n \t\t\t\t * dispatched with a `die()`.\n \t\t\t\t */\n-\t\t\t\treturn NULL;\n+\t\t\t\tif (ignore_missing_commits)\n+\t\t\t\t\treturn 0;\n+\t\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t\t     oid_to_hex(&p->object.oid));\n \t\t\t}\n \t\t\tp->object.flags |= flags;\n \t\t\tprio_queue_put(&queue, p);\n@@ -124,7 +128,7 @@ static struct commit_list *paint_down_to_common(struct repository *r,\n \t}\n \n \tclear_prio_queue(&queue);\n-\treturn result;\n+\treturn 0;\n }\n \n static struct commit_list *merge_bases_many(struct repository *r,\n@@ -151,7 +155,10 @@ static struct commit_list *merge_bases_many(struct repository *r,\n \t\t\treturn NULL;\n \t}\n \n-\tlist = paint_down_to_common(r, one, n, twos, 0, 0);\n+\tif (paint_down_to_common(r, one, n, twos, 0, 0, &list)) {\n+\t\tfree_commit_list(list);\n+\t\treturn NULL;\n+\t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n@@ -205,7 +212,7 @@ static int remove_redundant_no_gen(struct repository *r,\n \tfor (i = 0; i < cnt; i++)\n \t\trepo_parse_commit(r, array[i]);\n \tfor (i = 0; i < cnt; i++) {\n-\t\tstruct commit_list *common;\n+\t\tstruct commit_list *common = NULL;\n \t\ttimestamp_t min_generation = commit_graph_generation(array[i]);\n \n \t\tif (redundant[i])\n@@ -221,8 +228,16 @@ static int remove_redundant_no_gen(struct repository *r,\n \t\t\tif (curr_generation < min_generation)\n \t\t\t\tmin_generation = curr_generation;\n \t\t}\n-\t\tcommon = paint_down_to_common(r, array[i], filled,\n-\t\t\t\t\t      work, min_generation, 0);\n+\t\tif (paint_down_to_common(r, array[i], filled,\n+\t\t\t\t\t work, min_generation, 0, &common)) {\n+\t\t\tclear_commit_marks(array[i], all_flags);\n+\t\t\tclear_commit_marks_many(filled, work, all_flags);\n+\t\t\tfree_commit_list(common);\n+\t\t\tfree(work);\n+\t\t\tfree(redundant);\n+\t\t\tfree(filled_index);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (array[i]->object.flags & PARENT2)\n \t\t\tredundant[i] = 1;\n \t\tfor (j = 0; j < filled; j++)\n@@ -422,6 +437,10 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tclear_commit_marks_many(n, twos, all_flags);\n \n \tcnt = remove_redundant(r, rslt, cnt);\n+\tif (cnt < 0) {\n+\t\tfree(rslt);\n+\t\treturn NULL;\n+\t}\n \tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n \t\tcommit_list_insert_by_date(rslt[i], &result);\n@@ -491,7 +510,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \t\t\t     int nr_reference, struct commit **reference,\n \t\t\t     int ignore_missing_commits)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint ret = 0, i;\n \ttimestamp_t generation, max_generation = GENERATION_NUMBER_ZERO;\n \n@@ -510,10 +529,11 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit,\n \tif (generation > max_generation)\n \t\treturn ret;\n \n-\tbases = paint_down_to_common(r, commit,\n-\t\t\t\t     nr_reference, reference,\n-\t\t\t\t     generation, ignore_missing_commits);\n-\tif (commit->object.flags & PARENT2)\n+\tif (paint_down_to_common(r, commit,\n+\t\t\t\t nr_reference, reference,\n+\t\t\t\t generation, ignore_missing_commits, &bases))\n+\t\tret = -1;\n+\telse if (commit->object.flags & PARENT2)\n \t\tret = 1;\n \tclear_commit_marks(commit, all_flags);\n \tclear_commit_marks_many(nr_reference, reference, all_flags);\n@@ -566,6 +586,10 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \t\t}\n \t}\n \tnum_head = remove_redundant(the_repository, array, num_head);\n+\tif (num_head < 0) {\n+\t\tfree(array);\n+\t\treturn NULL;\n+\t}\n \tfor (i = 0; i < num_head; i++)\n \t\ttail = &commit_list_insert(array[i], tail)->next;\n \tfree(array);\n-- \ngitgitgadget\n\n"},{"id":"489602","messageId":"0f1ce130ce6fa868ea35ee7e96fa00aa52cc1435.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 06/11] commit-reach(merge_bases_many): pass on \"missing commits\" errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:12Z","receivedAt":"2024-02-28T09:44:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `paint_down_to_common()` function was just taught to indicate\nparsing errors, and now the `merge_bases_many()` function is aware of\nthat, too.\n\nOne tricky aspect is that `merge_bases_many()` parses commits of its\nown, but wants to gracefully handle the scenario where NULL is passed as\na merge head, returning the empty list of merge bases. The way this was\nhandled involved calling `repo_parse_commit(NULL)` and relying on it to\nreturn an error. This has to be done differently now so that we can\nhandle missing commits correctly by producing a fatal error.\n\nNext step: adjust the caller of `merge_bases_many()`:\n`get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 9ad5f9db4f7..ddfec5289dd 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -131,41 +131,49 @@ static int paint_down_to_common(struct repository *r,\n \treturn 0;\n }\n \n-static struct commit_list *merge_bases_many(struct repository *r,\n-\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t    struct commit **twos)\n+static int merge_bases_many(struct repository *r,\n+\t\t\t    struct commit *one, int n,\n+\t\t\t    struct commit **twos,\n+\t\t\t    struct commit_list **result)\n {\n \tstruct commit_list *list = NULL;\n-\tstruct commit_list *result = NULL;\n \tint i;\n \n \tfor (i = 0; i < n; i++) {\n-\t\tif (one == twos[i])\n+\t\tif (one == twos[i]) {\n \t\t\t/*\n \t\t\t * We do not mark this even with RESULT so we do not\n \t\t\t * have to clean it up.\n \t\t\t */\n-\t\t\treturn commit_list_insert(one, &result);\n+\t\t\t*result = commit_list_insert(one, result);\n+\t\t\treturn 0;\n+\t\t}\n \t}\n \n+\tif (!one)\n+\t\treturn 0;\n \tif (repo_parse_commit(r, one))\n-\t\treturn NULL;\n+\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t     oid_to_hex(&one->object.oid));\n \tfor (i = 0; i < n; i++) {\n+\t\tif (!twos[i])\n+\t\t\treturn 0;\n \t\tif (repo_parse_commit(r, twos[i]))\n-\t\t\treturn NULL;\n+\t\t\treturn error(_(\"could not parse commit %s\"),\n+\t\t\t\t     oid_to_hex(&twos[i]->object.oid));\n \t}\n \n \tif (paint_down_to_common(r, one, n, twos, 0, 0, &list)) {\n \t\tfree_commit_list(list);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n \n \twhile (list) {\n \t\tstruct commit *commit = pop_commit(&list);\n \t\tif (!(commit->object.flags & STALE))\n-\t\t\tcommit_list_insert_by_date(commit, &result);\n+\t\t\tcommit_list_insert_by_date(commit, result);\n \t}\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n@@ -410,10 +418,11 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tresult = merge_bases_many(r, one, n, twos);\n+\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n+\t\treturn NULL;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n \t\t\treturn result;\n-- \ngitgitgadget\n\n"},{"id":"489603","messageId":"133b69b6a62abb9317134636d5b323a33814e1ba.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 07/11] commit-reach(get_merge_bases_many_0): pass on \"missing commits\" errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:13Z","receivedAt":"2024-02-28T09:44:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate\nparsing errors, and now the `get_merge_bases_many_0()` function is aware\nof that, too.\n\nNext step: adjust the callers of `get_merge_bases_many_0()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n commit-reach.c | 57 +++++++++++++++++++++++++++++++-------------------\n 1 file changed, 36 insertions(+), 21 deletions(-)\n\ndiff --git a/commit-reach.c b/commit-reach.c\nindex ddfec5289dd..c0123eb7207 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -410,37 +410,38 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt\n \treturn remove_redundant_no_gen(r, array, cnt);\n }\n \n-static struct commit_list *get_merge_bases_many_0(struct repository *r,\n-\t\t\t\t\t\t  struct commit *one,\n-\t\t\t\t\t\t  int n,\n-\t\t\t\t\t\t  struct commit **twos,\n-\t\t\t\t\t\t  int cleanup)\n+static int get_merge_bases_many_0(struct repository *r,\n+\t\t\t\t  struct commit *one,\n+\t\t\t\t  int n,\n+\t\t\t\t  struct commit **twos,\n+\t\t\t\t  int cleanup,\n+\t\t\t\t  struct commit_list **result)\n {\n \tstruct commit_list *list;\n \tstruct commit **rslt;\n-\tstruct commit_list *result = NULL;\n \tint cnt, i;\n \n-\tif (merge_bases_many(r, one, n, twos, &result) < 0)\n-\t\treturn NULL;\n+\tif (merge_bases_many(r, one, n, twos, result) < 0)\n+\t\treturn -1;\n \tfor (i = 0; i < n; i++) {\n \t\tif (one == twos[i])\n-\t\t\treturn result;\n+\t\t\treturn 0;\n \t}\n-\tif (!result || !result->next) {\n+\tif (!*result || !(*result)->next) {\n \t\tif (cleanup) {\n \t\t\tclear_commit_marks(one, all_flags);\n \t\t\tclear_commit_marks_many(n, twos, all_flags);\n \t\t}\n-\t\treturn result;\n+\t\treturn 0;\n \t}\n \n \t/* There are more than one */\n-\tcnt = commit_list_count(result);\n+\tcnt = commit_list_count(*result);\n \tCALLOC_ARRAY(rslt, cnt);\n-\tfor (list = result, i = 0; list; list = list->next)\n+\tfor (list = *result, i = 0; list; list = list->next)\n \t\trslt[i++] = list->item;\n-\tfree_commit_list(result);\n+\tfree_commit_list(*result);\n+\t*result = NULL;\n \n \tclear_commit_marks(one, all_flags);\n \tclear_commit_marks_many(n, twos, all_flags);\n@@ -448,13 +449,12 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r,\n \tcnt = remove_redundant(r, rslt, cnt);\n \tif (cnt < 0) {\n \t\tfree(rslt);\n-\t\treturn NULL;\n+\t\treturn -1;\n \t}\n-\tresult = NULL;\n \tfor (i = 0; i < cnt; i++)\n-\t\tcommit_list_insert_by_date(rslt[i], &result);\n+\t\tcommit_list_insert_by_date(rslt[i], result);\n \tfree(rslt);\n-\treturn result;\n+\treturn 0;\n }\n \n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n@@ -462,7 +462,12 @@ struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      int n,\n \t\t\t\t\t      struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n@@ -470,14 +475,24 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    int n,\n \t\t\t\t\t\t    struct commit **twos)\n {\n-\treturn get_merge_bases_many_0(r, one, n, twos, 0);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n struct commit_list *repo_get_merge_bases(struct repository *r,\n \t\t\t\t\t struct commit *one,\n \t\t\t\t\t struct commit *two)\n {\n-\treturn get_merge_bases_many_0(r, one, 1, &two, 1);\n+\tstruct commit_list *result = NULL;\n+\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn NULL;\n+\t}\n+\treturn result;\n }\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"489604","messageId":"bd52f258cd9c810cc81060874043d382dd3be7c6.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 08/11] commit-reach(repo_get_merge_bases): pass on \"missing commits\" errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:14Z","receivedAt":"2024-02-28T09:44:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `repo_get_merge_bases()` macro) is aware of that, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext step: adjust the callers of `get_octopus_merge_bases()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/log.c                    | 10 +++++-----\n builtin/merge-tree.c             |  5 +++--\n builtin/merge.c                  | 20 ++++++++++++--------\n builtin/rebase.c                 |  8 +++++---\n builtin/rev-parse.c              |  5 +++--\n commit-reach.c                   | 23 +++++++++++------------\n commit-reach.h                   |  7 ++++---\n diff-lib.c                       |  5 +++--\n log-tree.c                       |  5 +++--\n merge-ort.c                      |  6 +++++-\n merge-recursive.c                |  4 +++-\n notes-merge.c                    |  3 ++-\n object-name.c                    |  7 +++++--\n revision.c                       | 12 ++++++++----\n sequencer.c                      |  8 ++++++--\n submodule.c                      |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 12 ++++++++++++\n 17 files changed, 96 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 1705da71aca..befafd6ae04 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1702,11 +1702,11 @@ static struct commit *get_base_commit(const char *base_commit,\n \t */\n \twhile (rev_nr > 1) {\n \t\tfor (i = 0; i < rev_nr / 2; i++) {\n-\t\t\tstruct commit_list *merge_base;\n-\t\t\tmerge_base = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  rev[2 * i],\n-\t\t\t\t\t\t\t  rev[2 * i + 1]);\n-\t\t\tif (!merge_base || merge_base->next) {\n+\t\t\tstruct commit_list *merge_base = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository,\n+\t\t\t\t\t\t rev[2 * i],\n+\t\t\t\t\t\t rev[2 * i + 1], &merge_base) < 0 ||\n+\t\t\t    !merge_base || merge_base->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"failed to find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex a35e0452d66..76200250629 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -463,8 +463,9 @@ static int real_merge(struct merge_tree_options *o,\n \t\t * Get the merge bases, in reverse order; see comment above\n \t\t * merge_incore_recursive in merge-ort.h\n \t\t */\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, parent1,\n-\t\t\t\t\t\t   parent2);\n+\t\tif (repo_get_merge_bases(the_repository, parent1,\n+\t\t\t\t\t parent2, &merge_bases) < 0)\n+\t\t\texit(128);\n \t\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d748d46e135..ac9d58adc29 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1517,10 +1517,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (!remoteheads)\n \t\t; /* already up-to-date */\n-\telse if (!remoteheads->next)\n-\t\tcommon = repo_get_merge_bases(the_repository, head_commit,\n-\t\t\t\t\t      remoteheads->item);\n-\telse {\n+\telse if (!remoteheads->next) {\n+\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t remoteheads->item, &common) < 0) {\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n+\t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n \t\tcommon = get_octopus_merge_bases(list);\n@@ -1631,7 +1634,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tstruct commit_list *j;\n \n \t\tfor (j = remoteheads; j; j = j->next) {\n-\t\t\tstruct commit_list *common_one;\n+\t\t\tstruct commit_list *common_one = NULL;\n \t\t\tstruct commit *common_item;\n \n \t\t\t/*\n@@ -1639,9 +1642,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t * merge_bases again, otherwise \"git merge HEAD^\n \t\t\t * HEAD^^\" would be missed.\n \t\t\t */\n-\t\t\tcommon_one = repo_get_merge_bases(the_repository,\n-\t\t\t\t\t\t\t  head_commit,\n-\t\t\t\t\t\t\t  j->item);\n+\t\t\tif (repo_get_merge_bases(the_repository, head_commit,\n+\t\t\t\t\t\t j->item, &common_one) < 0)\n+\t\t\t\texit(128);\n+\n \t\t\tcommon_item = common_one->item;\n \t\t\tfree_commit_list(common_one);\n \t\t\tif (!oideq(&common_item->object.oid, &j->item->object.oid)) {\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 043c65dccd9..06a55fc7325 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -879,7 +879,8 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!upstream)\n \t\tgoto done;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, upstream, head);\n+\tif (repo_get_merge_bases(the_repository, upstream, head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\tgoto done;\n \n@@ -898,8 +899,9 @@ static void fill_branch_base(struct rebase_options *options,\n {\n \tstruct commit_list *merge_bases = NULL;\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, options->onto,\n-\t\t\t\t\t   options->orig_head);\n+\tif (repo_get_merge_bases(the_repository, options->onto,\n+\t\t\t\t options->orig_head, &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases || merge_bases->next)\n \t\toidcpy(branch_base, null_oid());\n \telse\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex fde8861ca4e..c97d0f6144c 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -297,7 +297,7 @@ static int try_difference(const char *arg)\n \t\tshow_rev(NORMAL, &end_oid, end);\n \t\tshow_rev(symmetric ? NORMAL : REVERSED, &start_oid, start);\n \t\tif (symmetric) {\n-\t\t\tstruct commit_list *exclude;\n+\t\t\tstruct commit_list *exclude = NULL;\n \t\t\tstruct commit *a, *b;\n \t\t\ta = lookup_commit_reference(the_repository, &start_oid);\n \t\t\tb = lookup_commit_reference(the_repository, &end_oid);\n@@ -305,7 +305,8 @@ static int try_difference(const char *arg)\n \t\t\t\t*dotdot = '.';\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0)\n+\t\t\t\texit(128);\n \t\t\twhile (exclude) {\n \t\t\t\tstruct commit *commit = pop_commit(&exclude);\n \t\t\t\tshow_rev(REVERSED, &commit->object.oid, NULL);\ndiff --git a/commit-reach.c b/commit-reach.c\nindex c0123eb7207..b2e90687f7c 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -189,9 +189,12 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n \t\tfor (j = ret; j; j = j->next) {\n-\t\t\tstruct commit_list *bases;\n-\t\t\tbases = repo_get_merge_bases(the_repository, i->item,\n-\t\t\t\t\t\t     j->item);\n+\t\t\tstruct commit_list *bases = NULL;\n+\t\t\tif (repo_get_merge_bases(the_repository, i->item,\n+\t\t\t\t\t\t j->item, &bases) < 0) {\n+\t\t\t\tfree_commit_list(bases);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n \t\t\telse\n@@ -483,16 +486,12 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \treturn result;\n }\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *one,\n-\t\t\t\t\t struct commit *two)\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *one,\n+\t\t\t struct commit *two,\n+\t\t\t struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, 1, &two, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, 1, &two, 1, result);\n }\n \n /*\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 68f81549a44..2c6fcdd34f6 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -9,9 +9,10 @@ struct ref_filter;\n struct object_id;\n struct object_array;\n \n-struct commit_list *repo_get_merge_bases(struct repository *r,\n-\t\t\t\t\t struct commit *rev1,\n-\t\t\t\t\t struct commit *rev2);\n+int repo_get_merge_bases(struct repository *r,\n+\t\t\t struct commit *rev1,\n+\t\t\t struct commit *rev2,\n+\t\t\t struct commit_list **result);\n struct commit_list *repo_get_merge_bases_many(struct repository *r,\n \t\t\t\t\t      struct commit *one, int n,\n \t\t\t\t\t      struct commit **twos);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 0e9ec4f68af..498224ccce2 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -565,7 +565,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n {\n \tint i;\n \tstruct commit *mb_child[2] = {0};\n-\tstruct commit_list *merge_bases;\n+\tstruct commit_list *merge_bases = NULL;\n \n \tfor (i = 0; i < revs->pending.nr; i++) {\n \t\tstruct object *obj = revs->pending.objects[i].item;\n@@ -592,7 +592,8 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb)\n \t\tmb_child[1] = lookup_commit_reference(the_repository, &oid);\n \t}\n \n-\tmerge_bases = repo_get_merge_bases(the_repository, mb_child[0], mb_child[1]);\n+\tif (repo_get_merge_bases(the_repository, mb_child[0], mb_child[1], &merge_bases) < 0)\n+\t\texit(128);\n \tif (!merge_bases)\n \t\tdie(_(\"no merge base found\"));\n \tif (merge_bases->next)\ndiff --git a/log-tree.c b/log-tree.c\nindex 504da6b519e..4f337766a39 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1010,7 +1010,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \t\t\t   struct object_id *oid)\n {\n \tstruct merge_options o;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct merge_result res = {0};\n \tstruct pretty_print_context ctx = {0};\n \tstruct commit *parent1 = parents->item;\n@@ -1035,7 +1035,8 @@ static int do_remerge_diff(struct rev_info *opt,\n \t/* Parse the relevant commits and get the merge bases */\n \tparse_commit_or_die(parent1);\n \tparse_commit_or_die(parent2);\n-\tbases = repo_get_merge_bases(the_repository, parent1, parent2);\n+\tif (repo_get_merge_bases(the_repository, parent1, parent2, &bases) < 0)\n+\t\texit(128);\n \n \t/* Re-merge the parents */\n \tmerge_incore_recursive(&o, bases, parent1, parent2, &res);\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9f3af46333a..90d8495ca1f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5066,7 +5066,11 @@ static void merge_ort_internal(struct merge_options *opt,\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tresult->clean = -1;\n+\t\t\treturn;\n+\t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0d931cc14ad..d609373d960 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3638,7 +3638,9 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (!merge_bases) {\n-\t\tmerge_bases = repo_get_merge_bases(the_repository, h1, h2);\n+\t\tif (repo_get_merge_bases(the_repository, h1, h2,\n+\t\t\t\t\t &merge_bases) < 0)\n+\t\t\treturn -1;\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 8799b522a55..51282934ae6 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -607,7 +607,8 @@ int notes_merge(struct notes_merge_options *o,\n \tassert(local && remote);\n \n \t/* Find merge bases */\n-\tbases = repo_get_merge_bases(the_repository, local, remote);\n+\tif (repo_get_merge_bases(the_repository, local, remote, &bases) < 0)\n+\t\texit(128);\n \tif (!bases) {\n \t\tbase_oid = null_oid();\n \t\tbase_tree_oid = the_hash_algo->empty_tree;\ndiff --git a/object-name.c b/object-name.c\nindex 0bfa29dbbfe..63bec6d9a2b 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1481,7 +1481,7 @@ int repo_get_oid_mb(struct repository *r,\n \t\t    struct object_id *oid)\n {\n \tstruct commit *one, *two;\n-\tstruct commit_list *mbs;\n+\tstruct commit_list *mbs = NULL;\n \tstruct object_id oid_tmp;\n \tconst char *dots;\n \tint st;\n@@ -1509,7 +1509,10 @@ int repo_get_oid_mb(struct repository *r,\n \ttwo = lookup_commit_reference_gently(r, &oid_tmp, 0);\n \tif (!two)\n \t\treturn -1;\n-\tmbs = repo_get_merge_bases(r, one, two);\n+\tif (repo_get_merge_bases(r, one, two, &mbs) < 0) {\n+\t\tfree_commit_list(mbs);\n+\t\treturn -1;\n+\t}\n \tif (!mbs || mbs->next)\n \t\tst = -1;\n \telse {\ndiff --git a/revision.c b/revision.c\nindex 00d5c29bfce..eb0d550842f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1965,7 +1965,7 @@ static void add_pending_commit_list(struct rev_info *revs,\n \n static void prepare_show_merge(struct rev_info *revs)\n {\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tstruct commit *head, *other;\n \tstruct object_id oid;\n \tconst char **prune = NULL;\n@@ -1980,7 +1980,8 @@ static void prepare_show_merge(struct rev_info *revs)\n \tother = lookup_commit_or_die(&oid, \"MERGE_HEAD\");\n \tadd_pending_object(revs, &head->object, \"HEAD\");\n \tadd_pending_object(revs, &other->object, \"MERGE_HEAD\");\n-\tbases = repo_get_merge_bases(the_repository, head, other);\n+\tif (repo_get_merge_bases(the_repository, head, other, &bases) < 0)\n+\t\texit(128);\n \tadd_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);\n \tadd_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);\n \tfree_commit_list(bases);\n@@ -2068,14 +2069,17 @@ static int handle_dotdot_1(const char *arg, char *dotdot,\n \t} else {\n \t\t/* A...B -- find merge bases between the two */\n \t\tstruct commit *a, *b;\n-\t\tstruct commit_list *exclude;\n+\t\tstruct commit_list *exclude = NULL;\n \n \t\ta = lookup_commit_reference(revs->repo, &a_obj->oid);\n \t\tb = lookup_commit_reference(revs->repo, &b_obj->oid);\n \t\tif (!a || !b)\n \t\t\treturn dotdot_missing(arg, dotdot, revs, symmetric);\n \n-\t\texclude = repo_get_merge_bases(the_repository, a, b);\n+\t\tif (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) {\n+\t\t\tfree_commit_list(exclude);\n+\t\t\treturn -1;\n+\t\t}\n \t\tadd_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE,\n \t\t\t\t     flags_exclude);\n \t\tadd_pending_commit_list(revs, exclude, flags_exclude);\ndiff --git a/sequencer.c b/sequencer.c\nindex d584cac8ed9..4417f2f1956 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3913,7 +3913,7 @@ static int do_merge(struct repository *r,\n \tint run_commit_flags = 0;\n \tstruct strbuf ref_name = STRBUF_INIT;\n \tstruct commit *head_commit, *merge_commit, *i;\n-\tstruct commit_list *bases, *j;\n+\tstruct commit_list *bases = NULL, *j;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts.nr &&\n \t\t(!opts->strategy ||\n@@ -4139,7 +4139,11 @@ static int do_merge(struct repository *r,\n \t}\n \n \tmerge_commit = to_merge->item;\n-\tbases = repo_get_merge_bases(r, head_commit, merge_commit);\n+\tif (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) {\n+\t\tret = -1;\n+\t\tgoto leave_merge;\n+\t}\n+\n \tif (bases && oideq(&merge_commit->object.oid,\n \t\t\t   &bases->item->object.oid)) {\n \t\tret = 0;\ndiff --git a/submodule.c b/submodule.c\nindex e603a19a876..04931a5474b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -595,7 +595,12 @@ static void show_submodule_header(struct diff_options *o,\n \t     (!is_null_oid(two) && !*right))\n \t\tmessage = \"(commits not present)\";\n \n-\t*merge_bases = repo_get_merge_bases(sub, *left, *right);\n+\t*merge_bases = NULL;\n+\tif (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) {\n+\t\tmessage = \"(corrupt repository)\";\n+\t\tgoto output_header;\n+\t}\n+\n \tif (*merge_bases) {\n \t\tif ((*merge_bases)->item == *left)\n \t\t\tfast_forward = 1;\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex b2c8a43fce3..5d1e7aca4c8 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -945,4 +945,16 @@ test_expect_success 'check the input format when --stdin is passed' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out on missing commits as well' '\n+\tgit init --bare missing-commit.git &&\n+\tgit rev-list --objects side1 side3 >list-including-initial &&\n+\tgrep -v ^$(git rev-parse side1^) <list-including-initial >list &&\n+\tgit pack-objects missing-commit.git/objects/pack/missing-initial <list &&\n+\tside1=$(git rev-parse side1) &&\n+\tside3=$(git rev-parse side3) &&\n+\ttest_must_fail git --git-dir=missing-commit.git \\\n+\t\tmerge-tree --allow-unrelated-histories $side1 $side3 >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"489607","messageId":"b7ef90a57f0b8ec81bc3df484d981e89c5ed4057.1709113458.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 09/11] commit-reach(get_octopus_merge_bases): pass on \"missing commits\" errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:15Z","receivedAt":"2024-02-28T09:44:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases()` function (which is also\nsurfaced via the `get_merge_bases()` macro) is aware of that, too.\n\nNaturally, the callers need to be adjusted now, too.\n\nNext step: adjust `repo_get_merge_bases_many()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  8 ++++++--\n builtin/merge.c      |  6 +++++-\n builtin/pull.c       |  5 +++--\n commit-reach.c       | 20 +++++++++++---------\n commit-reach.h       |  2 +-\n 5 files changed, 26 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 0308fd73289..2edffc5487e 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -77,13 +77,17 @@ static int handle_independent(int count, const char **args)\n static int handle_octopus(int count, const char **args, int show_all)\n {\n \tstruct commit_list *revs = NULL;\n-\tstruct commit_list *result, *rev;\n+\tstruct commit_list *result = NULL, *rev;\n \tint i;\n \n \tfor (i = count - 1; i >= 0; i--)\n \t\tcommit_list_insert(get_commit_reference(args[i]), &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0) {\n+\t\tfree_commit_list(revs);\n+\t\tfree_commit_list(result);\n+\t\treturn 128;\n+\t}\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ac9d58adc29..94c5b693972 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1526,7 +1526,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t} else {\n \t\tstruct commit_list *list = remoteheads;\n \t\tcommit_list_insert(head_commit, &list);\n-\t\tcommon = get_octopus_merge_bases(list);\n+\t\tif (get_octopus_merge_bases(list, &common) < 0) {\n+\t\t\tfree(list);\n+\t\t\tret = 2;\n+\t\t\tgoto done;\n+\t\t}\n \t\tfree(list);\n \t}\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e6f2942c0c5..0c5a55f2f4d 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -820,7 +820,7 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tconst struct object_id *merge_head,\n \t\tconst struct object_id *fork_point)\n {\n-\tstruct commit_list *revs = NULL, *result;\n+\tstruct commit_list *revs = NULL, *result = NULL;\n \n \tcommit_list_insert(lookup_commit_reference(the_repository, curr_head),\n \t\t\t   &revs);\n@@ -830,7 +830,8 @@ static int get_octopus_merge_base(struct object_id *merge_base,\n \t\tcommit_list_insert(lookup_commit_reference(the_repository, fork_point),\n \t\t\t\t   &revs);\n \n-\tresult = get_octopus_merge_bases(revs);\n+\tif (get_octopus_merge_bases(revs, &result) < 0)\n+\t\texit(128);\n \tfree_commit_list(revs);\n \treduce_heads_replace(&result);\n \ndiff --git a/commit-reach.c b/commit-reach.c\nindex b2e90687f7c..288d3d896a2 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -176,24 +176,26 @@ static int merge_bases_many(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result)\n {\n-\tstruct commit_list *i, *j, *k, *ret = NULL;\n+\tstruct commit_list *i, *j, *k;\n \n \tif (!in)\n-\t\treturn ret;\n+\t\treturn 0;\n \n-\tcommit_list_insert(in->item, &ret);\n+\tcommit_list_insert(in->item, result);\n \n \tfor (i = in->next; i; i = i->next) {\n \t\tstruct commit_list *new_commits = NULL, *end = NULL;\n \n-\t\tfor (j = ret; j; j = j->next) {\n+\t\tfor (j = *result; j; j = j->next) {\n \t\t\tstruct commit_list *bases = NULL;\n \t\t\tif (repo_get_merge_bases(the_repository, i->item,\n \t\t\t\t\t\t j->item, &bases) < 0) {\n \t\t\t\tfree_commit_list(bases);\n-\t\t\t\treturn NULL;\n+\t\t\t\tfree_commit_list(*result);\n+\t\t\t\t*result = NULL;\n+\t\t\t\treturn -1;\n \t\t\t}\n \t\t\tif (!new_commits)\n \t\t\t\tnew_commits = bases;\n@@ -202,10 +204,10 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in)\n \t\t\tfor (k = bases; k; k = k->next)\n \t\t\t\tend = k;\n \t\t}\n-\t\tfree_commit_list(ret);\n-\t\tret = new_commits;\n+\t\tfree_commit_list(*result);\n+\t\t*result = new_commits;\n \t}\n-\treturn ret;\n+\treturn 0;\n }\n \n static int remove_redundant_no_gen(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 2c6fcdd34f6..4690b6ecd0c 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -21,7 +21,7 @@ struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\n \t\t\t\t\t\t    struct commit **twos);\n \n-struct commit_list *get_octopus_merge_bases(struct commit_list *in);\n+int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n int repo_is_descendant_of(struct repository *r,\n \t\t\t  struct commit *commit,\n-- \ngitgitgadget\n\n"},{"id":"489605","messageId":"32587b3caa701cf548de08ddf8a32a80e7de2325.1709113459.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 10/11] commit-reach(repo_get_merge_bases_many): pass on \"missing commits\" errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:16Z","receivedAt":"2024-02-28T09:44:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many()` function is aware of\nthat, too.\n\nNaturally, there are a lot of callers that need to be adjusted now, too.\n\nNext stop: `repo_get_merge_bases_dirty()`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n bisect.c              |  7 ++++---\n builtin/log.c         | 13 +++++++------\n commit-reach.c        | 16 ++++++----------\n commit-reach.h        |  7 ++++---\n commit.c              |  7 ++++---\n t/helper/test-reach.c |  9 ++++++---\n 6 files changed, 31 insertions(+), 28 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex 1be8e0a2711..2018466d69f 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -851,10 +851,11 @@ static void handle_skipped_merge_base(const struct object_id *mb)\n static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)\n {\n \tenum bisect_error res = BISECT_OK;\n-\tstruct commit_list *result;\n+\tstruct commit_list *result = NULL;\n \n-\tresult = repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n-\t\t\t\t\t   rev + 1);\n+\tif (repo_get_merge_bases_many(the_repository, rev[0], rev_nr - 1,\n+\t\t\t\t      rev + 1, &result) < 0)\n+\t\texit(128);\n \n \tfor (; result; result = result->next) {\n \t\tconst struct object_id *mb = &result->item->object.oid;\ndiff --git a/builtin/log.c b/builtin/log.c\nindex befafd6ae04..c75790a7cec 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1656,7 +1656,7 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\tstruct branch *curr_branch = branch_get(NULL);\n \t\tconst char *upstream = branch_get_upstream(curr_branch, NULL);\n \t\tif (upstream) {\n-\t\t\tstruct commit_list *base_list;\n+\t\t\tstruct commit_list *base_list = NULL;\n \t\t\tstruct commit *commit;\n \t\t\tstruct object_id oid;\n \n@@ -1667,11 +1667,12 @@ static struct commit *get_base_commit(const char *base_commit,\n \t\t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tcommit = lookup_commit_or_die(&oid, \"upstream base\");\n-\t\t\tbase_list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t      commit, total,\n-\t\t\t\t\t\t\t      list);\n-\t\t\t/* There should be one and only one merge base. */\n-\t\t\tif (!base_list || base_list->next) {\n+\t\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t\t      commit, total,\n+\t\t\t\t\t\t      list,\n+\t\t\t\t\t\t      &base_list) < 0 ||\n+\t\t\t    /* There should be one and only one merge base. */\n+\t\t\t    !base_list || base_list->next) {\n \t\t\t\tif (die_on_failure) {\n \t\t\t\t\tdie(_(\"could not find exact merge base\"));\n \t\t\t\t} else {\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 288d3d896a2..0aeaef25343 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -462,17 +462,13 @@ static int get_merge_bases_many_0(struct repository *r,\n \treturn 0;\n }\n \n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one,\n-\t\t\t\t\t      int n,\n-\t\t\t\t\t      struct commit **twos)\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one,\n+\t\t\t      int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 1, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 4690b6ecd0c..458043f4d58 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -13,9 +13,10 @@ int repo_get_merge_bases(struct repository *r,\n \t\t\t struct commit *rev1,\n \t\t\t struct commit *rev2,\n \t\t\t struct commit_list **result);\n-struct commit_list *repo_get_merge_bases_many(struct repository *r,\n-\t\t\t\t\t      struct commit *one, int n,\n-\t\t\t\t\t      struct commit **twos);\n+int repo_get_merge_bases_many(struct repository *r,\n+\t\t\t      struct commit *one, int n,\n+\t\t\t      struct commit **twos,\n+\t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n \t\t\t\t\t\t    struct commit *one, int n,\ndiff --git a/commit.c b/commit.c\nindex 8405d7c3fce..00add5d81c6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1054,7 +1054,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n {\n \tstruct object_id oid;\n \tstruct rev_collect revs;\n-\tstruct commit_list *bases;\n+\tstruct commit_list *bases = NULL;\n \tint i;\n \tstruct commit *ret = NULL;\n \tchar *full_refname;\n@@ -1079,8 +1079,9 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)\n \tfor (i = 0; i < revs.nr; i++)\n \t\trevs.commit[i]->object.flags &= ~TMP_MARK;\n \n-\tbases = repo_get_merge_bases_many(the_repository, commit, revs.nr,\n-\t\t\t\t\t  revs.commit);\n+\tif (repo_get_merge_bases_many(the_repository, commit, revs.nr,\n+\t\t\t\t      revs.commit, &bases) < 0)\n+\t\texit(128);\n \n \t/*\n \t * There should be one and only one merge base, when we found\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex aa816e168ea..84ee9da8681 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -117,9 +117,12 @@ int cmd__reach(int ac, const char **av)\n \telse if (!strcmp(av[1], \"is_descendant_of\"))\n \t\tprintf(\"%s(A,X):%d\\n\", av[1], repo_is_descendant_of(r, A, X));\n \telse if (!strcmp(av[1], \"get_merge_bases_many\")) {\n-\t\tstruct commit_list *list = repo_get_merge_bases_many(the_repository,\n-\t\t\t\t\t\t\t\t     A, X_nr,\n-\t\t\t\t\t\t\t\t     X_array);\n+\t\tstruct commit_list *list = NULL;\n+\t\tif (repo_get_merge_bases_many(the_repository,\n+\t\t\t\t\t      A, X_nr,\n+\t\t\t\t\t      X_array,\n+\t\t\t\t\t      &list) < 0)\n+\t\t\texit(128);\n \t\tprintf(\"%s(A,X):\\n\", av[1]);\n \t\tprint_sorted_commit_ids(list);\n \t} else if (!strcmp(av[1], \"reduce_heads\")) {\n-- \ngitgitgadget\n\n"},{"id":"489606","messageId":"05de9f2444477dd38b7e1cf67d8d233ce6e0e68b.1709113459.git.gitgitgadget@gmail.com","threadId":"60905","inReplyTo":"pull.1657.v4.git.1709113457.gitgitgadget@gmail.com","subject":"[PATCH v4 11/11] commit-reach(repo_get_merge_bases_many_dirty): pass on errors","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-28T09:44:17Z","receivedAt":"2024-02-28T09:44:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\n(Actually, this commit is only about passing on \"missing commits\"\nerrors, but adding that to the commit's title would have made it too\nlong.)\n\nThe `merge_bases_many()` function was just taught to indicate parsing\nerrors, and now the `repo_get_merge_bases_many_dirty()` function is\naware of that, too.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-base.c |  9 ++++++---\n commit-reach.c       | 16 ++++++----------\n commit-reach.h       |  7 ++++---\n 3 files changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/merge-base.c b/builtin/merge-base.c\nindex 2edffc5487e..a8a1ca53968 100644\n--- a/builtin/merge-base.c\n+++ b/builtin/merge-base.c\n@@ -13,10 +13,13 @@\n \n static int show_merge_base(struct commit **rev, int rev_nr, int show_all)\n {\n-\tstruct commit_list *result, *r;\n+\tstruct commit_list *result = NULL, *r;\n \n-\tresult = repo_get_merge_bases_many_dirty(the_repository, rev[0],\n-\t\t\t\t\t\t rev_nr - 1, rev + 1);\n+\tif (repo_get_merge_bases_many_dirty(the_repository, rev[0],\n+\t\t\t\t\t    rev_nr - 1, rev + 1, &result) < 0) {\n+\t\tfree_commit_list(result);\n+\t\treturn -1;\n+\t}\n \n \tif (!result)\n \t\treturn 1;\ndiff --git a/commit-reach.c b/commit-reach.c\nindex 0aeaef25343..202776b29e9 100644\n--- a/commit-reach.c\n+++ b/commit-reach.c\n@@ -471,17 +471,13 @@ int repo_get_merge_bases_many(struct repository *r,\n \treturn get_merge_bases_many_0(r, one, n, twos, 1, result);\n }\n \n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one,\n-\t\t\t\t\t\t    int n,\n-\t\t\t\t\t\t    struct commit **twos)\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one,\n+\t\t\t\t    int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result)\n {\n-\tstruct commit_list *result = NULL;\n-\tif (get_merge_bases_many_0(r, one, n, twos, 0, &result) < 0) {\n-\t\tfree_commit_list(result);\n-\t\treturn NULL;\n-\t}\n-\treturn result;\n+\treturn get_merge_bases_many_0(r, one, n, twos, 0, result);\n }\n \n int repo_get_merge_bases(struct repository *r,\ndiff --git a/commit-reach.h b/commit-reach.h\nindex 458043f4d58..bf63cc468fd 100644\n--- a/commit-reach.h\n+++ b/commit-reach.h\n@@ -18,9 +18,10 @@ int repo_get_merge_bases_many(struct repository *r,\n \t\t\t      struct commit **twos,\n \t\t\t      struct commit_list **result);\n /* To be used only when object flags after this call no longer matter */\n-struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,\n-\t\t\t\t\t\t    struct commit *one, int n,\n-\t\t\t\t\t\t    struct commit **twos);\n+int repo_get_merge_bases_many_dirty(struct repository *r,\n+\t\t\t\t    struct commit *one, int n,\n+\t\t\t\t    struct commit **twos,\n+\t\t\t\t    struct commit_list **result);\n \n int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);\n \n-- \ngitgitgadget\n"},{"id":"489623","messageId":"xmqqedcwjq04.fsf@gitster.g","threadId":"60905","inReplyTo":"837aa5a89c640427a5de064da93f1de4934d8212.1709113458.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-02-28T20:24:59Z","receivedAt":"2024-02-28T20:25:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>\n> When `git fetch --update-shallow` needs to test for commit ancestry, it\n> can naturally run into a missing object (e.g. if it is a parent of a\n> shallow commit). For the purpose of `--update-shallow`, this needs to be\n> treated as if the child commit did not even have that parent, i.e. the\n> commit history needs to be clamped.\n>\n> For all other scenarios, clamping the commit history is actually a bug,\n> as it would hide repository corruption (for an analysis regarding\n> shallow and partial clones, see the analysis further down).\n>\n> Add a flag to optionally ask the function to ignore missing commits, as\n> `--update-shallow` needs it to, while detecting missing objects as a\n> repository corruption error by default.\n>\n> This flag is needed, and cannot replaced by `is_repository_shallow()` to\n> indicate that situation, because that function would return 0 in the\n> `--update-shallow` scenario: There is not actually a `shallow` file in\n> that scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\n> with receive.updateshallow on\") and t5538.4 (\"add new shallow root with\n> receive.updateshallow on\").\n\nNicely written.\n\nThe description above that has been totally revamped reads much much\nclearer, at least to me, compared to the previous round.  \n\nShould we declare the topic done and mark it for 'next'?\n\nThanks.\n"},{"id":"489624","messageId":"gh7cioqp8p.fsf@gouders.net","threadId":"60905","inReplyTo":"xmqqedcwjq04.fsf@gitster.g","subject":"Re: [PATCH v4 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits","fromName":"Dirk Gouders","fromEmail":"dirk@gouders.net","sentAt":"2024-02-28T20:59:34Z","receivedAt":"2024-02-28T20:59:55Z","isPatch":true,"sender":{"key":"dirk@gouders.net","avatar":"https://avatars.githubusercontent.com/u/81326422?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n>> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n>>\n>> When `git fetch --update-shallow` needs to test for commit ancestry, it\n>> can naturally run into a missing object (e.g. if it is a parent of a\n>> shallow commit). For the purpose of `--update-shallow`, this needs to be\n>> treated as if the child commit did not even have that parent, i.e. the\n>> commit history needs to be clamped.\n>>\n>> For all other scenarios, clamping the commit history is actually a bug,\n>> as it would hide repository corruption (for an analysis regarding\n>> shallow and partial clones, see the analysis further down).\n>>\n>> Add a flag to optionally ask the function to ignore missing commits, as\n>> `--update-shallow` needs it to, while detecting missing objects as a\n>> repository corruption error by default.\n>>\n>> This flag is needed, and cannot replaced by `is_repository_shallow()` to\n>> indicate that situation, because that function would return 0 in the\n>> `--update-shallow` scenario: There is not actually a `shallow` file in\n>> that scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\n>> with receive.updateshallow on\") and t5538.4 (\"add new shallow root with\n>> receive.updateshallow on\").\n>\n> Nicely written.\n>\n> The description above that has been totally revamped reads much much\n> clearer, at least to me, compared to the previous round.  \n>\n> Should we declare the topic done and mark it for 'next'?\n>\n> Thanks.\n\nI agree that this text reads much clearer -- even to me with close to\nzero experience, here.\n\nThank you for taking the time to rewrite the text, Johannes.\n\nDirk\n"},{"id":"489653","messageId":"a044e7aa-6a71-0fa6-c6f9-db6a08d8c9f5@gmx.de","threadId":"60905","inReplyTo":"xmqqedcwjq04.fsf@gitster.g","subject":"Re: [PATCH v4 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-02-29T09:53:29Z","receivedAt":"2024-02-29T09:53:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 28 Feb 2024, Junio C Hamano wrote:\n\n> \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >\n> > When `git fetch --update-shallow` needs to test for commit ancestry, it\n> > can naturally run into a missing object (e.g. if it is a parent of a\n> > shallow commit). For the purpose of `--update-shallow`, this needs to be\n> > treated as if the child commit did not even have that parent, i.e. the\n> > commit history needs to be clamped.\n> >\n> > For all other scenarios, clamping the commit history is actually a bug,\n> > as it would hide repository corruption (for an analysis regarding\n> > shallow and partial clones, see the analysis further down).\n> >\n> > Add a flag to optionally ask the function to ignore missing commits, as\n> > `--update-shallow` needs it to, while detecting missing objects as a\n> > repository corruption error by default.\n> >\n> > This flag is needed, and cannot replaced by `is_repository_shallow()` to\n\nHrmpf. I just spotted the missing \"be\" between \"cannot\" and \"replaced\".\n\nJunio, would you kindly amend the commit message accordingly?\n\n> > indicate that situation, because that function would return 0 in the\n> > `--update-shallow` scenario: There is not actually a `shallow` file in\n> > that scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\n> > with receive.updateshallow on\") and t5538.4 (\"add new shallow root with\n> > receive.updateshallow on\").\n>\n> Nicely written.\n>\n> The description above that has been totally revamped reads much much\n> clearer, at least to me, compared to the previous round.\n\nThank you!\n\n> Should we declare the topic done and mark it for 'next'?\n\nAfter you looked over the correctness of the patches, I would be\ncomfortable with that.\n\nThanks!\nJohannes\n"},{"id":"489654","messageId":"7552da7e-2aa3-73bc-2e55-304b09f280f8@gmx.de","threadId":"60905","inReplyTo":"gh7cioqp8p.fsf@gouders.net","subject":"Re: [PATCH v4 04/11] commit-reach(paint_down_to_common): prepare for handling shallow commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2024-02-29T09:54:25Z","receivedAt":"2024-02-29T09:54:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Dirk,\n\nOn Wed, 28 Feb 2024, Dirk Gouders wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > \"Johannes Schindelin via GitGitGadget\" <gitgitgadget@gmail.com>\n> > writes:\n> >\n> >> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> >>\n> >> When `git fetch --update-shallow` needs to test for commit ancestry, it\n> >> can naturally run into a missing object (e.g. if it is a parent of a\n> >> shallow commit). For the purpose of `--update-shallow`, this needs to be\n> >> treated as if the child commit did not even have that parent, i.e. the\n> >> commit history needs to be clamped.\n> >>\n> >> For all other scenarios, clamping the commit history is actually a bug,\n> >> as it would hide repository corruption (for an analysis regarding\n> >> shallow and partial clones, see the analysis further down).\n> >>\n> >> Add a flag to optionally ask the function to ignore missing commits, as\n> >> `--update-shallow` needs it to, while detecting missing objects as a\n> >> repository corruption error by default.\n> >>\n> >> This flag is needed, and cannot replaced by `is_repository_shallow()` to\n> >> indicate that situation, because that function would return 0 in the\n> >> `--update-shallow` scenario: There is not actually a `shallow` file in\n> >> that scenario, as demonstrated e.g. by t5537.10 (\"add new shallow root\n> >> with receive.updateshallow on\") and t5538.4 (\"add new shallow root with\n> >> receive.updateshallow on\").\n> >\n> > Nicely written.\n> >\n> > The description above that has been totally revamped reads much much\n> > clearer, at least to me, compared to the previous round.\n> >\n> > Should we declare the topic done and mark it for 'next'?\n> >\n> > Thanks.\n>\n> I agree that this text reads much clearer -- even to me with close to\n> zero experience, here.\n>\n> Thank you for taking the time to rewrite the text, Johannes.\n\nThank _you_ for taking the time to review the patches and help me with\nimproving them!\n\nCiao,\nJohannes\n"},{"id":"489727","messageId":"20240301065647.GA2680308@coredump.intra.peff.net","threadId":"60905","inReplyTo":"4dd214f91d4783f29b03908cc0034156253889a7.1708608110.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/11] Start reporting missing commits in `repo_in_merge_bases_many()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-03-01T06:56:47Z","receivedAt":"2024-03-01T06:56:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 22, 2024 at 01:21:42PM +0000, Johannes Schindelin via GitGitGadget wrote:\n\n> @@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n>  \t\t\t\t\t\t\t&o->oid,\n>  \t\t\t\t\t\t\t&a->oid,\n>  \t\t\t\t\t\t\t&b->oid);\n> +\t\t\tif (result->clean < 0)\n> +\t\t\t\treturn -1;\n\nCoverity flagged this code as NO_EFFECT. The issue is that result->clean\nis an unsigned single-bit boolean, so it can never be negative. If we\nexpand the context a bit, it's\n\n                        result->clean = merge_submodule(opt, &result->blob.oid,\n                                                        o->path,\n                                                        &o->oid,\n                                                        &a->oid,\n                                                        &b->oid);\n                        if (result->clean < 0)\n                                return -1;\n\nSo it's possible there's also a problem in the existing assignment, as a\nnegative return from merge_submodule() would be truncated. But from my\nread of it, it will always return either 0 or 1 (if it sees errors from\nrepo_in_merge_bases(), for example, it will output an error message and\nreturn 0).\n\nSo I think we'd want either:\n\n  1. Drop this hunk, and let result->clean stay as a pure boolean.\n\n  2. Propagate errors from merge_submodule() using -1, in which case the\n     code needs to be more like:\n\n       int ret = merge_submodule(...);\n       if (ret < 0)\n               return -1;\n       result->clean = !!ret;\n\nI didn't follow the series closely enough to know which would be better.\nI guess alternatively it could be a tri-state that carries the error\naround (it looks like it is in ort's \"struct merge_result\"), but that\nprobably means auditing all of the spots that look at \"clean\" to see\nwhat they would do with a negative value.\n\n-Peff\n"},{"id":"489728","messageId":"20240301065805.GB2680308@coredump.intra.peff.net","threadId":"60905","inReplyTo":"1938b317a49f4d688bfefd7e5a74ad750a55a91a.1709113458.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/11] commit-reach(repo_in_merge_bases_many): report missing commits","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-03-01T06:58:05Z","receivedAt":"2024-03-01T06:58:06Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 28, 2024 at 09:44:09AM +0000, Johannes Schindelin via GitGitGadget wrote:\n\n> @@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n>  \t\t\t\t\t\t\t&o->oid,\n>  \t\t\t\t\t\t\t&a->oid,\n>  \t\t\t\t\t\t\t&b->oid);\n> +\t\t\tif (result->clean < 0)\n> +\t\t\t\treturn -1;\n\nSorry, I accidentally commented on v2 of your series a moment ago,\nrather than the most recent version. But this hunk was untouched between\nthe two, so the comment still applies:\n\n  https://lore.kernel.org/git/20240301065647.GA2680308@coredump.intra.peff.net/\n\n-Peff\n"},{"id":"489744","messageId":"xmqqo7by7wno.fsf@gitster.g","threadId":"60905","inReplyTo":"20240301065805.GB2680308@coredump.intra.peff.net","subject":"Re: [PATCH v4 03/11] commit-reach(repo_in_merge_bases_many): report missing commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-01T16:18:51Z","receivedAt":"2024-03-01T16:18:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Feb 28, 2024 at 09:44:09AM +0000, Johannes Schindelin via GitGitGadget wrote:\n>\n>> @@ -1402,6 +1436,8 @@ static int merge_mode_and_contents(struct merge_options *opt,\n>>  \t\t\t\t\t\t\t&o->oid,\n>>  \t\t\t\t\t\t\t&a->oid,\n>>  \t\t\t\t\t\t\t&b->oid);\n>> +\t\t\tif (result->clean < 0)\n>> +\t\t\t\treturn -1;\n>\n> Sorry, I accidentally commented on v2 of your series a moment ago,\n> rather than the most recent version. But this hunk was untouched between\n> the two, so the comment still applies:\n>\n>   https://lore.kernel.org/git/20240301065647.GA2680308@coredump.intra.peff.net/\n>\n> -Peff\n\nThanks for spotting.\n\nThe topic now is in 'next' so let's fix it incrementally while I'll\nhold it there.  If all of us thought it has seen enough eyeballs and\nis good enough for 'next', yet we later find there was something we\nall missed, that is worth a separate explanation, e.g., \"The primary\nmotivation behind the series is still good, but for such and such\nreasons we missed this case we are fixing.\"\n\n"}]}