{"thread":{"id":"57963","subject":"[PATCH] submodule merge: update conflict error message","startedAt":"2022-06-06T23:55:23Z","lastAt":"2022-08-16T19:39:56Z","messageCount":60,"participants":["Calvin Wan","Junio C Hamano","Glen Choo","Elijah Newren","Philippe Blain","Johannes Schindelin","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"456765","messageId":"20220606235449.2890858-1-calvinwan@google.com","threadId":"57963","inReplyTo":null,"subject":"[PATCH] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-06T23:54:49Z","receivedAt":"2022-06-06T23:55:23Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to do a non-fast-forward merge in a project with\nconflicts in the submodules, the merge fails and git prints the\nfollowing error:\n\nFailed to merge submodule <submodules>\nCONFLICT (submodule):Merge conflict in <submodule>\nAutomatic merge failed; fix conflicts and then commit the result.\n\nGit is left in a conflicted state, which requires the user to:\n 1. abort the merge\n 2. merge submodules\n 3. merge superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers. \n\nUpdate error message to the following when attempting to do a\nnon-fast-forward merge in a project with conflicts in the submodules.\nThe error message is based off of what would happen when `merge\n--recurse-submodules` is eventually supported\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; recursive merging with submodules is currently\nnot supported. To manually merge, the following steps are recommended:\n - abort the current merge\n - merge submodules individually\n - merge superproject\n\nI considered automatically aborting the merge if git detects the merge\nfailed because of a submodule conflict, however, doing so causes a\nsignificant amount of tests in `t7610-mergetool.sh` (and some other test\nscripts as well) to fail, suggesting users have come to expect this\nstate and have their workarounds with `git mergetool`\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n\n---\n builtin/merge.c            | 12 +++++++++++-\n merge-ort.c                |  4 +++-\n merge-recursive.c          |  4 +++-\n merge-recursive.h          |  1 +\n t/t6437-submodule-merge.sh |  5 ++++-\n 5 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex f178f5a3ee..39f5ee66d6 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -88,6 +88,7 @@ static const char *sign_commit;\n static int autostash;\n static int no_verify;\n static char *into_name;\n+static int submodule_conflict = 0;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  NO_TRIVIAL },\n@@ -757,6 +758,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\telse\n \t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n \t\t\t\t\t\treversed, &result);\n+\t\tif (o.submodule_conflict)\n+\t\t\tsubmodule_conflict = 1;\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -973,7 +976,14 @@ static int suggest_conflicts(void)\n \tstrbuf_release(&msgbuf);\n \tfclose(fp);\n \trepo_rerere(the_repository, allow_rerere_auto);\n-\tprintf(_(\"Automatic merge failed; \"\n+\tif (submodule_conflict)\n+\t\tprintf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n+\t\t\t\"not supported. To manually merge, the following steps are recommended:\\n\"\n+\t\t\t\" - abort the current merge\\n\"\n+\t\t\t\" - merge submodules individually\\n\"\n+\t\t\t\" - merge superproject\\n\"));\n+\telse\n+\t\tprintf(_(\"Automatic merge failed; \"\n \t\t\t\"fix conflicts and then commit the result.\\n\"));\n \treturn 1;\n }\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0d3f42592f..205d6658bc 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -3866,8 +3866,10 @@ static void process_entry(struct merge_options *opt,\n \t\t\tconst char *reason = _(\"content\");\n \t\t\tif (ci->filemask == 6)\n \t\t\t\treason = _(\"add/add\");\n-\t\t\tif (S_ISGITLINK(merged_file.mode))\n+\t\t\tif (S_ISGITLINK(merged_file.mode)) {\n \t\t\t\treason = _(\"submodule\");\n+\t\t\t\topt->submodule_conflict = 1;\n+\t\t\t}\n \t\t\tpath_msg(opt, path, 0,\n \t\t\t\t _(\"CONFLICT (%s): Merge conflict in %s\"),\n \t\t\t\t reason, path);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fd1bbde061..535b8cc758 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3149,8 +3149,10 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t}\n \n \tif (!mfi->clean) {\n-\t\tif (S_ISGITLINK(mfi->blob.mode))\n+\t\tif (S_ISGITLINK(mfi->blob.mode)) {\n \t\t\treason = _(\"submodule\");\n+\t\t\topt->submodule_conflict = 1;\n+\t\t}\n \t\toutput(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n \t\t\t\treason, path);\n \t\tif (ci && !df_conflict_remains)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b88000e3c2..6fd31644ad 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -51,6 +51,7 @@ struct merge_options {\n \n \t/* internal fields used by the implementation */\n \tstruct merge_options_internal *priv;\n+\tunsigned submodule_conflict : 1;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 178413c22f..bfcc81cd06 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n \t grep $(cat expect) actual > /dev/null &&\n+\t test_i18ngrep \"recursive merging with submodules is currently\" actual &&\n \t git reset --hard)\n '\n \n@@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t fi &&\n \tgrep $(cat expect1) actual > /dev/null &&\n \tgrep $(cat expect2) actual > /dev/null &&\n+\ttest_i18ngrep \"recursive merging with submodules is currently\" actual &&\n \tgit reset --hard)\n '\n \n@@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\ttest_i18ngrep \"recursive merging with submodules is currently\" actual)\n '\n \n \n\nbase-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n-- \n2.36.1.255.ge46751e96f-goog\n\n"},{"id":"456767","messageId":"xmqqh74x1eol.fsf@gitster.g","threadId":"57963","inReplyTo":"20220606235449.2890858-1-calvinwan@google.com","subject":"Re: [PATCH] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-07T00:48:42Z","receivedAt":"2022-06-07T00:48:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> When attempting to do a non-fast-forward merge in a project with\n> conflicts in the submodules, the merge fails and git prints the\n> following error:\n>\n> Failed to merge submodule <submodules>\n> CONFLICT (submodule):Merge conflict in <submodule>\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. abort the merge\n>  2. merge submodules\n>  3. merge superproject\n> These steps are non-obvious for newer submodule users to figure out\n\nHmph.  Is 1. necessary?\n\nIOW, based on the information we already have (we may not be\nsurfacing, which can be corrected), wouldn't it be easier to instead\n(A) go to submodule and make a merge and then (B) come back to the\nsuperproject, \"git add <submodule\" to record the result of submodule\nmerge, and say \"git commit\" to conclude?\n\nThe thing I am worried most about is that you may be throwing away\ninformation that would help the user by aborting the superproject\nmerge.  Before doing so, you have stage #2 and stage #3 of the\nsubmodule commit, so which commits in the submodule you need to\nmerge in (A) above should be fairly clear.  If you abort the merge\nfirst, how does the user know which commits in the submodule the\nuser needs to merge?\n\n> The error message is based off of what would happen when `merge\n> --recurse-submodules` is eventually supported\n\nOK.\n\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Automatic merge failed; recursive merging with submodules is currently\n> not supported. To manually merge, the following steps are recommended:\n>  - abort the current merge\n>  - merge submodules individually\n>  - merge superproject\n\nAgain, I am not sure about the recommendation.  The message saying\n\"currently not supported\" I think is a good idea.\n\n> I considered automatically aborting the merge if git detects the merge\n> failed because of a submodule conflict, however, doing so causes a\n> significant amount of tests in `t7610-mergetool.sh` (and some other test\n> scripts as well) to fail, suggesting users have come to expect this\n> state and have their workarounds with `git mergetool`\n\nWith or without test failures, my gut feeling sais that it cannot be\na good idea to automatically abort the merge, without first grabbing\nsome information out of the conflicted state.\n\nThanks.\n"},{"id":"456876","messageId":"CAFySSZC0ieObo=CzhoHiqrcdv3335VaRBtYMLFe9oEJ0hucOCA@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqh74x1eol.fsf@gitster.g","subject":"Re: [PATCH] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-08T17:19:26Z","receivedAt":"2022-06-08T17:25:13Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> Hmph.  Is 1. necessary?\n\nI just tested it and it is not, so I do agree recommending to abort the\nmerge is unnecessary/bad advice. How does this sound?\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; recursive merging with submodules is currently\nnot supported. To manually merge, merge conflicted submodules first\nbefore merging the superproject.\n\nOn Mon, Jun 6, 2022 at 5:48 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Calvin Wan <calvinwan@google.com> writes:\n>\n> > When attempting to do a non-fast-forward merge in a project with\n> > conflicts in the submodules, the merge fails and git prints the\n> > following error:\n> >\n> > Failed to merge submodule <submodules>\n> > CONFLICT (submodule):Merge conflict in <submodule>\n> > Automatic merge failed; fix conflicts and then commit the result.\n> >\n> > Git is left in a conflicted state, which requires the user to:\n> >  1. abort the merge\n> >  2. merge submodules\n> >  3. merge superproject\n> > These steps are non-obvious for newer submodule users to figure out\n>\n> Hmph.  Is 1. necessary?\n>\n> IOW, based on the information we already have (we may not be\n> surfacing, which can be corrected), wouldn't it be easier to instead\n> (A) go to submodule and make a merge and then (B) come back to the\n> superproject, \"git add <submodule\" to record the result of submodule\n> merge, and say \"git commit\" to conclude?\n>\n> The thing I am worried most about is that you may be throwing away\n> information that would help the user by aborting the superproject\n> merge.  Before doing so, you have stage #2 and stage #3 of the\n> submodule commit, so which commits in the submodule you need to\n> merge in (A) above should be fairly clear.  If you abort the merge\n> first, how does the user know which commits in the submodule the\n> user needs to merge?\n>\n> > The error message is based off of what would happen when `merge\n> > --recurse-submodules` is eventually supported\n>\n> OK.\n>\n> > Failed to merge submodule <submodule>\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Automatic merge failed; recursive merging with submodules is currently\n> > not supported. To manually merge, the following steps are recommended:\n> >  - abort the current merge\n> >  - merge submodules individually\n> >  - merge superproject\n>\n> Again, I am not sure about the recommendation.  The message saying\n> \"currently not supported\" I think is a good idea.\n>\n> > I considered automatically aborting the merge if git detects the merge\n> > failed because of a submodule conflict, however, doing so causes a\n> > significant amount of tests in `t7610-mergetool.sh` (and some other test\n> > scripts as well) to fail, suggesting users have come to expect this\n> > state and have their workarounds with `git mergetool`\n>\n> With or without test failures, my gut feeling sais that it cannot be\n> a good idea to automatically abort the merge, without first grabbing\n> some information out of the conflicted state.\n>\n> Thanks.\n"},{"id":"456877","messageId":"kl6l8rq76oub.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"57963","inReplyTo":"CAFySSZC0ieObo=CzhoHiqrcdv3335VaRBtYMLFe9oEJ0hucOCA@mail.gmail.com","subject":"Re: [PATCH] submodule merge: update conflict error message","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-06-08T17:34:52Z","receivedAt":"2022-06-08T17:35:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n>> Hmph.  Is 1. necessary?\n>\n> I just tested it and it is not, so I do agree recommending to abort the\n> merge is unnecessary/bad advice. How does this sound?\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Automatic merge failed; recursive merging with submodules is currently\n> not supported. To manually merge, merge conflicted submodules first\n> before merging the superproject.\n\nThis message sounds ok to me, since this is probably what the user wants\n90% of the time. Since we don't abort the merge, this just a 'regular'\nmerge conflict resolution (albeit with submodules). The user probably\nwants to merge the submodules, but they can choose however they want to\nresolve the merge conflict, e.g. maybe they'd prefer to just pick one\nside (or even more exotically, a different commit altogether.)\n\nAn improvement for that other 10% would be to print this help message\nwith the advice() API so that users can turn it off if they don't find\nit helpful. Or maybe it's confusing to some new users who use a\ndifferent merging workflow and so an admin might turn off this advice\nfor them.\n"},{"id":"456878","messageId":"CAFySSZBV8r1PuLb7br90VHbHmXgdgW2Ta4qw07BN9SXvh=aFpw@mail.gmail.com","threadId":"57963","inReplyTo":"kl6l8rq76oub.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-08T18:01:00Z","receivedAt":"2022-06-08T18:01:16Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> The user probably\n> wants to merge the submodules, but they can choose however they want to\n> resolve the merge conflict\n\nIt sounds like I should reword \"merge conflicted submodules\" to\n\"resolve conflicted submodules\". That should cover those 10% cases.\n\nI would prefer to find a generic, but still helpful message that doesn't\nrequire going into the advice() API or require some config change\n\nOn Wed, Jun 8, 2022 at 10:35 AM Glen Choo <chooglen@google.com> wrote:\n>\n> Calvin Wan <calvinwan@google.com> writes:\n>\n> >> Hmph.  Is 1. necessary?\n> >\n> > I just tested it and it is not, so I do agree recommending to abort the\n> > merge is unnecessary/bad advice. How does this sound?\n> >\n> > Failed to merge submodule <submodule>\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Automatic merge failed; recursive merging with submodules is currently\n> > not supported. To manually merge, merge conflicted submodules first\n> > before merging the superproject.\n>\n> This message sounds ok to me, since this is probably what the user wants\n> 90% of the time. Since we don't abort the merge, this just a 'regular'\n> merge conflict resolution (albeit with submodules). The user probably\n> wants to merge the submodules, but they can choose however they want to\n> resolve the merge conflict, e.g. maybe they'd prefer to just pick one\n> side (or even more exotically, a different commit altogether.)\n>\n> An improvement for that other 10% would be to print this help message\n> with the advice() API so that users can turn it off if they don't find\n> it helpful. Or maybe it's confusing to some new users who use a\n> different merging workflow and so an admin might turn off this advice\n> for them.\n"},{"id":"456880","messageId":"xmqqy1y7q87x.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZBV8r1PuLb7br90VHbHmXgdgW2Ta4qw07BN9SXvh=aFpw@mail.gmail.com","subject":"Re: [PATCH] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-08T19:13:38Z","receivedAt":"2022-06-08T19:13:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n>> The user probably\n>> wants to merge the submodules, but they can choose however they want to\n>> resolve the merge conflict\n>\n> It sounds like I should reword \"merge conflicted submodules\" to\n> \"resolve conflicted submodules\". That should cover those 10% cases.\n>\n> I would prefer to find a generic, but still helpful message that doesn't\n> require going into the advice() API or require some config change\n\nWe want the users not to blow away the half-merged state in the\nworking tree.  We are guiding them to first go into submodules and\nmerge (in which case, we should tell them merge what with what---I\nthink the first parent should be what they have checked out there,\nbut the other parent, which is what is recorded in the tree of the\nsuperproject commit being merged as gitlink, may not be at the tip\nof any branch you have in the submodule).  And then they come back\nto the superproject and resolve the conflict in the working tree and\nthe index.\n\n> > Failed to merge submodule <submodule>\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Automatic merge failed; recursive merging with submodules is currently\n> > not supported. To manually merge, merge conflicted submodules first\n> > before merging the superproject.\n\nSo,\n\n    to manually complete the merge:\n    - go to submodule A, and merge commit a24c4e37d0\n    - go to submodule B, and merge commit a6f14c960b\n    - come back to superproject, and \"git add A B\" to record the above merge\n    - in superproject, resolve the other conflicts\n    - commit the resulting index in the superproject\n\nor something along that line, perhaps?\n\n\n\n"},{"id":"457049","messageId":"20220610231152.2594428-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220606235449.2890858-1-calvinwan@google.com","subject":"[PATCH v2] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-10T23:11:52Z","receivedAt":"2022-06-10T23:13:03Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to do a non-fast-forward merge in a project with\nconflicts in the submodules, the merge fails and git prints the\nfollowing error:\n\nFailed to merge submodule <submodules>\nCONFLICT (submodule):Merge conflict in <submodule>\nAutomatic merge failed; fix conflicts and then commit the result.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules\n 2. add submodules changes to the superproject\n 3. merge superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers. \n\nUpdate error message to the following when attempting to do a\nnon-fast-forward merge in a project with conflicts in the submodules.\nThe error message is based off of what would happen when `merge\n--recurse-submodules` is eventually supported\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; recursive merging with submodules is currently\nnot supported. To manually complete the merge:\n - go to submodule (<submodule>), and merge commit <commit>\n - come back to superproject, and `git add <submodule>` to record the above merge \n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\n\nChanges since v1:\n - Removed advice to abort merge\n - Error message updated to contain more commit/submodule information\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n\n---\n builtin/merge.c            | 23 ++++++++++++++++++++++-\n merge-ort.c                |  7 ++++++-\n merge-recursive.c          |  7 ++++++-\n merge-recursive.h          |  4 ++++\n t/t6437-submodule-merge.sh |  5 ++++-\n 5 files changed, 42 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex f178f5a3ee..7e2deea7fb 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -88,6 +88,8 @@ static const char *sign_commit;\n static int autostash;\n static int no_verify;\n static char *into_name;\n+static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n+static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  NO_TRIVIAL },\n@@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t}\n \n \t\tinit_merge_options(&o, the_repository);\n+\t\to.conflicted_submodule_oids = &conflicted_submodule_oids;\n+\t\to.conflicted_submodule_paths = &conflicted_submodule_paths;\n \t\tif (!strcmp(strategy, \"subtree\"))\n \t\t\to.subtree_shift = \"\";\n \n@@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n \tstrbuf_release(&msgbuf);\n \tfclose(fp);\n \trepo_rerere(the_repository, allow_rerere_auto);\n-\tprintf(_(\"Automatic merge failed; \"\n+\tif (conflicted_submodule_oids.nr > 0) {\n+\t\tint i;\n+\t\tprintf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n+\t\t\t\"not supported. To manually complete the merge:\\n\"));\n+\t\tfor (i = 0; i < conflicted_submodule_oids.nr; i++) {\n+\t\t\tprintf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n+\t\t\t\tconflicted_submodule_paths.items[i].string,\n+\t\t\t\toid_to_hex(&conflicted_submodule_oids.oid[i]));\n+\t\t}\n+\t\tprintf(_(\" - come back to superproject, and `git add\"));\n+\t\tfor (i = 0; i < conflicted_submodule_paths.nr; i++)\n+\t\t\tprintf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n+\t\tprintf(_(\"` to record the above merge \\n\"\n+\t\t\" - resolve any other conflicts in the superproject\\n\"\n+\t\t\" - commit the resulting index in the superproject\\n\"));\n+\t} else {\n+\t\tprintf(_(\"Automatic merge failed; \"\n \t\t\t\"fix conflicts and then commit the result.\\n\"));\n+\t}\n \treturn 1;\n }\n \ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0d3f42592f..c86ee11614 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n \t\t\tconst char *reason = _(\"content\");\n \t\t\tif (ci->filemask == 6)\n \t\t\t\treason = _(\"add/add\");\n-\t\t\tif (S_ISGITLINK(merged_file.mode))\n+\t\t\tif (S_ISGITLINK(merged_file.mode)) {\n \t\t\t\treason = _(\"submodule\");\n+\t\t\t\tif (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n+\t\t\t\t\toid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n+\t\t\t\t\tstring_list_append(opt->conflicted_submodule_paths, path);\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tpath_msg(opt, path, 0,\n \t\t\t\t _(\"CONFLICT (%s): Merge conflict in %s\"),\n \t\t\t\t reason, path);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fd1bbde061..ff7cdbefe9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t}\n \n \tif (!mfi->clean) {\n-\t\tif (S_ISGITLINK(mfi->blob.mode))\n+\t\tif (S_ISGITLINK(mfi->blob.mode)) {\n \t\t\treason = _(\"submodule\");\n+\t\t\tif (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n+\t\t\t\toid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n+\t\t\t\tstring_list_append(opt->conflicted_submodule_paths, path);\n+\t\t\t}\n+\t\t}\n \t\toutput(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n \t\t\t\treason, path);\n \t\tif (ci && !df_conflict_remains)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b88000e3c2..5d267e7a43 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -51,6 +51,10 @@ struct merge_options {\n \n \t/* internal fields used by the implementation */\n \tstruct merge_options_internal *priv;\n+\n+\t/* fields that hold submodule conflict information */\n+\tstruct oid_array *conflicted_submodule_oids;\n+\tstruct string_list *conflicted_submodule_paths;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 178413c22f..5b384dedc1 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n \t grep $(cat expect) actual > /dev/null &&\n+\t test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n \t git reset --hard)\n '\n \n@@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t fi &&\n \tgrep $(cat expect1) actual > /dev/null &&\n \tgrep $(cat expect2) actual > /dev/null &&\n+\ttest_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n \tgit reset --hard)\n '\n \n@@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\ttest_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n '\n \n \n-- \n2.36.1.476.g0c4daa206d-goog\n\n"},{"id":"457059","messageId":"CABPp-BHe7wdd3LMYtZ83ZgapvUxzQYcK-3=sdMAD5Ozs4YmKyw@mail.gmail.com","threadId":"57963","inReplyTo":"20220610231152.2594428-1-calvinwan@google.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-11T04:53:42Z","receivedAt":"2022-06-11T04:54:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> When attempting to do a non-fast-forward merge in a project with\n> conflicts in the submodules, the merge fails and git prints the\n> following error:\n>\n> Failed to merge submodule <submodules>\n> CONFLICT (submodule):Merge conflict in <submodule>\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. merge submodules\n>  2. add submodules changes to the superproject\n>  3. merge superproject\n\nI think we may need to tweak these steps a bit:\n\n   1. merge submodules OR update submodules to an already existing\ncommit that reflects the merge (i.e. as submodules they may well be\nindependently actively developed.  Someone may have already merged the\nappropriate branch/commit and the already extant merges should be used\nin preference to creating new ones.)\n   2. <just as you said>\n   3. FINISH merging the superproject (i.e. don't redo the merge)\n\nI might be off on step 1; I have only used submodules extremely\nlightly and usually only for a limited time, so I'm not really sure\nwhat the expected workflow is.  I could also imagine it potentially\nbeing repository-dependent whether you would want to merge or select\nan appropriate commit to update to.\n\n> These steps are non-obvious for newer submodule users to figure out\n> based on the error message and neither `git submodule status` nor `git\n> status` provide any useful pointers.\n>\n> Update error message to the following when attempting to do a\n> non-fast-forward merge in a project with conflicts in the submodules.\n\nMake sense.\n\n> The error message is based off of what would happen when `merge\n> --recurse-submodules` is eventually supported\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Automatic merge failed; recursive merging with submodules is currently\n> not supported. To manually complete the merge:\n>  - go to submodule (<submodule>), and merge commit <commit>\n>  - come back to superproject, and `git add <submodule>` to record the above merge\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n\nAh, I see you've fixed step 3 here; that's good.\n\nHowever, these steps miss out on the merge-or-update submodule\npossibility...and since you mention these steps are potentially the\nbasis for some future work, I think it's worth calling that out again.\nI'm slightly worried that the 'update' part of merge-or-update may\nthrow a wrench in the plans for `merge --recurse-submodules`.\n\n(Also, continuing on the `merge --recurse-submodules` talent but\ndiscussing a different aspect of it, I'm curious if you need to add\nextra dirty-worktree/dirty-index checks for each submodule at the\nstart of a merge, whether you need to try to lock N indexes before\nstarting, and what other extra details are necessary.  But those are\nprobably questions to address whenever work on the future series to\nimplement this option is underway.)\n\n> Changes since v1:\n>  - Removed advice to abort merge\n>  - Error message updated to contain more commit/submodule information\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n>\n> ---\n>  builtin/merge.c            | 23 ++++++++++++++++++++++-\n>  merge-ort.c                |  7 ++++++-\n>  merge-recursive.c          |  7 ++++++-\n>  merge-recursive.h          |  4 ++++\n>  t/t6437-submodule-merge.sh |  5 ++++-\n>  5 files changed, 42 insertions(+), 4 deletions(-)\n\nSo you're modifying the \"git merge\" porcelain level (builtin/merge.c),\nthe two merges strategies, their common header, and adding some tests.\nNo other porcelains are modified...\n\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index f178f5a3ee..7e2deea7fb 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -88,6 +88,8 @@ static const char *sign_commit;\n>  static int autostash;\n>  static int no_verify;\n>  static char *into_name;\n> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n>\n>  static struct strategy all_strategy[] = {\n>         { \"recursive\",  NO_TRIVIAL },\n> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n>                 }\n>\n>                 init_merge_options(&o, the_repository);\n> +               o.conflicted_submodule_oids = &conflicted_submodule_oids;\n> +               o.conflicted_submodule_paths = &conflicted_submodule_paths;\n>                 if (!strcmp(strategy, \"subtree\"))\n>                         o.subtree_shift = \"\";\n>\n> @@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n>         strbuf_release(&msgbuf);\n>         fclose(fp);\n>         repo_rerere(the_repository, allow_rerere_auto);\n> -       printf(_(\"Automatic merge failed; \"\n> +       if (conflicted_submodule_oids.nr > 0) {\n> +               int i;\n> +               printf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n> +                       \"not supported. To manually complete the merge:\\n\"));\n> +               for (i = 0; i < conflicted_submodule_oids.nr; i++) {\n> +                       printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n> +                               conflicted_submodule_paths.items[i].string,\n> +                               oid_to_hex(&conflicted_submodule_oids.oid[i]));\n> +               }\n> +               printf(_(\" - come back to superproject, and `git add\"));\n> +               for (i = 0; i < conflicted_submodule_paths.nr; i++)\n> +                       printf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n> +               printf(_(\"` to record the above merge \\n\"\n> +               \" - resolve any other conflicts in the superproject\\n\"\n> +               \" - commit the resulting index in the superproject\\n\"));\n> +       } else {\n> +               printf(_(\"Automatic merge failed; \"\n>                         \"fix conflicts and then commit the result.\\n\"));\n> +       }\n>         return 1;\n>  }\n\nThis is kind of nice.  I was worried you were going to embed these\nmessages in the merge strategies, which could cause problems for other\nusers of the merge strategies such as the --remerge-diff options to\ngit log and git show (your new messages would be unwanted noise or\neven cause confusion there), and to the merge-tree work.  In fact, a\ncurrent submodule-merging message (search for \"--cacheinfo\") that is\npotentially similar to what you are adding here but which was added at\nthe merge strategy level already feels highly problematic to me.  I've\nbeen considering nuking it from the codebase for some time because of\nthose issues, though I guess just moving it out elsewhere may also\nwork.\n\nHowever, this implementation does have a drawback: these messages\nwon't appear for rebases, cherry-picks, reverts, attempted unstashing\n(git stash apply/pop), or other actions unless you update the relevant\nporcelains for those as well.\n\nA possible alternative here would be to move it to the level of\nmerge-recursive and merge-ort that is only called when the working\ntree and index are updated.  For example, placing it in\nmerge_finalize() in merge-recursive.c and merge_switch_to_result() in\nmerge-ort.c -- next to the diff_warn_rename_limit() call in each case.\nHowever, I'm also fine with keeping it at the porcelain level, it just\nmay need to be in a function that is called from several porcelains\nthat way.\n\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 0d3f42592f..c86ee11614 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n>                         const char *reason = _(\"content\");\n>                         if (ci->filemask == 6)\n>                                 reason = _(\"add/add\");\n> -                       if (S_ISGITLINK(merged_file.mode))\n> +                       if (S_ISGITLINK(merged_file.mode)) {\n>                                 reason = _(\"submodule\");\n> +                               if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> +                                       oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n> +                                       string_list_append(opt->conflicted_submodule_paths, path);\n> +                               }\n> +                       }\n>                         path_msg(opt, path, 0,\n>                                  _(\"CONFLICT (%s): Merge conflict in %s\"),\n>                                  reason, path);\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index fd1bbde061..ff7cdbefe9 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>         }\n>\n>         if (!mfi->clean) {\n> -               if (S_ISGITLINK(mfi->blob.mode))\n> +               if (S_ISGITLINK(mfi->blob.mode)) {\n>                         reason = _(\"submodule\");\n> +                       if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> +                               oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n> +                               string_list_append(opt->conflicted_submodule_paths, path);\n> +                       }\n> +               }\n>                 output(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n>                                 reason, path);\n>                 if (ci && !df_conflict_remains)\n\nNice that the changes needed to both the ort and recursive strategies\nare so localized.  :-)\n\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index b88000e3c2..5d267e7a43 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -51,6 +51,10 @@ struct merge_options {\n>\n>         /* internal fields used by the implementation */\n>         struct merge_options_internal *priv;\n> +\n> +       /* fields that hold submodule conflict information */\n> +       struct oid_array *conflicted_submodule_oids;\n> +       struct string_list *conflicted_submodule_paths;\n>  };\n\nMake sense.\n\n>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index 178413c22f..5b384dedc1 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n>          grep $(cat expect) actual > /dev/null &&\n> +        test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          fi &&\n>         grep $(cat expect1) actual > /dev/null &&\n>         grep $(cat expect2) actual > /dev/null &&\n> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n>         git reset --hard)\n>  '\n>\n> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>         git commit -a -m \"f\" &&\n>\n>         git checkout -b test-backward e &&\n> -       test_must_fail git merge f)\n> +       test_must_fail git merge f >actual &&\n> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n\ntest_i18ngrep is apparently on the way out:\n\n    $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh\n    # Wrapper for grep which used to be used for\n    # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other\n    # in-flight changes. Should not be used and will be removed soon.\n    test_i18ngrep () {\n\nI think you just want to use grep instead here for each of these hunks.\n"},{"id":"457068","messageId":"ed62e9ad-536f-3ab8-2972-17c7f8996617@gmail.com","threadId":"57963","inReplyTo":"CABPp-BHe7wdd3LMYtZ83ZgapvUxzQYcK-3=sdMAD5Ozs4YmKyw@mail.gmail.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-06-11T17:08:50Z","receivedAt":"2022-06-11T17:08:58Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Calvin and Elijah,\n\nLe 2022-06-11 à 00:53, Elijah Newren a écrit :\n> On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote:\n>>\n>> When attempting to do a non-fast-forward merge in a project with\n>> conflicts in the submodules, the merge fails and git prints the\n>> following error:\n\nI think we would want to be slightly more precise here, as \n\"conflicts in the submodules\" could be understood to mean:\n\n1) conflicting submodule pointers in the superproject being merged\n2) 1. + also content conflicts in the submodule merge\n\nHere we are just talking about 1.\n\nAlso, the merge does not automatically fail, it only fails if\na fast-forward *of the submodule pointer* is not possible, which \nmight be what you meant above; but to me \"a non-fast-forward merge in a project\nwith conflict in the submodules\" read like the non-fast-forwardness being talked about\nwas in the superproject, not in the submodule(s).\n\n[message 0]\n>>\n>> Failed to merge submodule <submodules>\n>> CONFLICT (submodule):Merge conflict in <submodule>\n>> Automatic merge failed; fix conflicts and then commit the result.\n\n   Aside: the first <submodules> should be singular.\n\nThis is indeed the output you get with the ort strategy if no existing merge commit\nexist in the submodule repository that merges the submodule pointers recorded\nin the superproject branches being merged. With the older \"recursive\" strategy,\nthis message is:\n\n[message 1]\nFailed to merge submodule sub (merge following commits not found)\nAuto-merging sub\nCONFLICT (submodule): Merge conflict in sub\nAutomatic merge failed; fix conflicts and then commit the result\n\nc73cda76b1 (merge-ort: copy and adapt merge_submodule() \nfrom merge-recursive.c, 2021-01-01) does not mention why that error message\nwas changed, but perhaps it is just because it is slightly confusing\nto the user (they might not be expecting Git to look for an existing\nmerge and so they don't know what merge the message is talking about).\n\nMaybe something like \"failed to find existing commit merging <hash1> and <hash2>\"\nwould be clearer...\n\n>>\n>> Git is left in a conflicted state, which requires the user to:\n>>  1. merge submodules\n>>  2. add submodules changes to the superproject\n>>  3. merge superproject\n> \n> I think we may need to tweak these steps a bit:\n> \n>    1. merge submodules OR update submodules to an already existing\n> commit that reflects the merge (i.e. as submodules they may well be\n> independently actively developed.  Someone may have already merged the\n> appropriate branch/commit and the already extant merges should be used\n> in preference to creating new ones.)\n>    2. <just as you said>\n>    3. FINISH merging the superproject (i.e. don't redo the merge)\n> \n> I might be off on step 1; I have only used submodules extremely\n> lightly and usually only for a limited time, so I'm not really sure\n> what the expected workflow is.  I could also imagine it potentially\n> being repository-dependent whether you would want to merge or select\n> an appropriate commit to update to.\n\nI agree with Elijah here, the submodule conflcit resolution might be to:\n\n1) just choose one of the existing submodule commits on either side of \nthe superproject branches being merged\n2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first)\n3) create such a merge commit (what you are talking about here)\n\nI also agree that it is highly repository- and workflow-dependent what\nthe \"right\" resolution is.\n\nNote that the code does try to find an existing merge commit in the submodule\nrepository, in this case the error message is different. If such a merge commit \nexists:\n\n    [message 2]\n    Failed to merge submodule sub, but a possible merge resolution exists:\n        aafcfa2 Merge branch 'sub-c' into sub-d\n\n\n    If this is correct simply add it to the index for example\n    by using:\n\n      git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 \"sub\"\n\n    which will accept this suggestion.\n\n    CONFLICT (submodule): Merge conflict in sub\n    Automatic merge failed; fix conflicts and then commit the result.\n\nif multiple merge exist:\n\n    [message 3]\n    Failed to merge submodule sub, but multiple possible merges exist:\n        2729a0c Merge branch 'sub-c' into ambiguous\n        aafcfa2 Merge branch 'sub-c' into sub-d\n\n    CONFLICT (submodule): Merge conflict in sub\n    Automatic merge failed; fix conflicts and then commit the result.\n\nAnother aside, I really don't think we should instruct users to run\nplumbing like 'git update-index --cacheinfo' , they should just cd into\nthe submodule and checkout the merge commit!\n\n> \n>> These steps are non-obvious for newer submodule users to figure out\n>> based on the error message and neither `git submodule status` nor `git\n>> status` provide any useful pointers.\n>>\n>> Update error message to the following when attempting to do a\n>> non-fast-forward merge in a project with conflicts in the submodules.\n> \n> Make sense.\n\nI agree that more guidance is a very nice addition.\n\nRegarding 'git status' output, it is downright confusing, since it says:\n\nUnmerged paths:\n  (use \"git add <file>...\" to mark resolution)\n\tboth modified:   sub\n\nwhich is not at all what you want to do most of the time (that would\njust stage whatever the currently checked out commit in the submodule is\nat the moment!)\n\n> \n>> The error message is based off of what would happen when `merge\n>> --recurse-submodules` is eventually supported\n>>\n>> Failed to merge submodule <submodule>\n>> CONFLICT (submodule): Merge conflict in <submodule>\n>> Automatic merge failed; recursive merging with submodules is currently\n>> not supported. To manually complete the merge:\n>>  - go to submodule (<submodule>), and merge commit <commit>\n>>  - come back to superproject, and `git add <submodule>` to record the above merge\n>>  - resolve any other conflicts in the superproject\n>>  - commit the resulting index in the superproject\n> \n> Ah, I see you've fixed step 3 here; that's good.\n> \n> However, these steps miss out on the merge-or-update submodule\n> possibility...and since you mention these steps are potentially the\n> basis for some future work, I think it's worth calling that out again.\n> I'm slightly worried that the 'update' part of merge-or-update may\n> throw a wrench in the plans for `merge --recurse-submodules`.\n> \n\nSlightly off topic here, but for me the most important improvement that\n'git merge --recurse-submodules' would bring is when there is *no submodule\nconflicts*, i.e. one side fast-forwards the submodule and the other side\ndoes not touch it, since in that case the worktree of the submodule *is not updated*\nby the current code, which is one of the most confusing aspect of using submodules\nfor new users (\"why is \"git status\" and \"git diff\" not clean if \"git merge\"\nwas fast-forward ?!?\"), and the same is true (maybe more even so) for 'git rebase'. \n\n> (Also, continuing on the `merge --recurse-submodules` talent but\n> discussing a different aspect of it, I'm curious if you need to add\n> extra dirty-worktree/dirty-index checks for each submodule at the\n> start of a merge, whether you need to try to lock N indexes before\n> starting, and what other extra details are necessary.  But those are\n> probably questions to address whenever work on the future series to\n> implement this option is underway.)\n> \n>> Changes since v1:\n>>  - Removed advice to abort merge\n>>  - Error message updated to contain more commit/submodule information\n>>\n>> Signed-off-by: Calvin Wan <calvinwan@google.com>\n>>\n>> ---\n>>  builtin/merge.c            | 23 ++++++++++++++++++++++-\n>>  merge-ort.c                |  7 ++++++-\n>>  merge-recursive.c          |  7 ++++++-\n>>  merge-recursive.h          |  4 ++++\n>>  t/t6437-submodule-merge.sh |  5 ++++-\n>>  5 files changed, 42 insertions(+), 4 deletions(-)\n> \n> So you're modifying the \"git merge\" porcelain level (builtin/merge.c),\n> the two merges strategies, their common header, and adding some tests.\n> No other porcelains are modified...\n> \n>> diff --git a/builtin/merge.c b/builtin/merge.c\n>> index f178f5a3ee..7e2deea7fb 100644\n>> --- a/builtin/merge.c\n>> +++ b/builtin/merge.c\n>> @@ -88,6 +88,8 @@ static const char *sign_commit;\n>>  static int autostash;\n>>  static int no_verify;\n>>  static char *into_name;\n>> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n>> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n>>\n>>  static struct strategy all_strategy[] = {\n>>         { \"recursive\",  NO_TRIVIAL },\n>> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n>>                 }\n>>\n>>                 init_merge_options(&o, the_repository);\n>> +               o.conflicted_submodule_oids = &conflicted_submodule_oids;\n>> +               o.conflicted_submodule_paths = &conflicted_submodule_paths;\n>>                 if (!strcmp(strategy, \"subtree\"))\n>>                         o.subtree_shift = \"\";\n>>\n>> @@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n>>         strbuf_release(&msgbuf);\n>>         fclose(fp);\n>>         repo_rerere(the_repository, allow_rerere_auto);\n>> -       printf(_(\"Automatic merge failed; \"\n>> +       if (conflicted_submodule_oids.nr > 0) {\n>> +               int i;\n>> +               printf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n>> +                       \"not supported. To manually complete the merge:\\n\"));\n>> +               for (i = 0; i < conflicted_submodule_oids.nr; i++) {\n>> +                       printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n>> +                               conflicted_submodule_paths.items[i].string,\n>> +                               oid_to_hex(&conflicted_submodule_oids.oid[i]));\n>> +               }\n>> +               printf(_(\" - come back to superproject, and `git add\"));\n>> +               for (i = 0; i < conflicted_submodule_paths.nr; i++)\n>> +                       printf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n>> +               printf(_(\"` to record the above merge \\n\"\n>> +               \" - resolve any other conflicts in the superproject\\n\"\n>> +               \" - commit the resulting index in the superproject\\n\"));\n>> +       } else {\n>> +               printf(_(\"Automatic merge failed; \"\n>>                         \"fix conflicts and then commit the result.\\n\"));\n>> +       }\n>>         return 1;\n>>  }\n> \n> This is kind of nice.  I was worried you were going to embed these\n> messages in the merge strategies, which could cause problems for other\n> users of the merge strategies such as the --remerge-diff options to\n> git log and git show (your new messages would be unwanted noise or\n> even cause confusion there), and to the merge-tree work.  In fact, a\n> current submodule-merging message (search for \"--cacheinfo\") that is\n> potentially similar to what you are adding here but which was added at\n> the merge strategy level already feels highly problematic to me.  I've\n> been considering nuking it from the codebase for some time because of\n> those issues, though I guess just moving it out elsewhere may also\n> work.\n> \n\nYes, this is the message I copied above. I agree that if we can tweak this\nadvice to instead mention 'git checkout' and add it to the message \nthat Calvin is adding in this series, it would make for a really better\nUX.\n\n> However, this implementation does have a drawback: these messages\n> won't appear for rebases, cherry-picks, reverts, attempted unstashing\n> (git stash apply/pop), or other actions unless you update the relevant\n> porcelains for those as well.\n> \n> A possible alternative here would be to move it to the level of\n> merge-recursive and merge-ort that is only called when the working\n> tree and index are updated.  For example, placing it in\n> merge_finalize() in merge-recursive.c and merge_switch_to_result() in\n> merge-ort.c -- next to the diff_warn_rename_limit() call in each case.\n> However, I'm also fine with keeping it at the porcelain level, it just\n> may need to be in a function that is called from several porcelains\n> that way.\n\nI think moving it to merge_finalize / merge_switch_to_result is indeed a \ngood suggestion, then we might be improving the UX across the board and not just \nfor 'git merge'. \n\n> \n>> diff --git a/merge-ort.c b/merge-ort.c\n>> index 0d3f42592f..c86ee11614 100644\n>> --- a/merge-ort.c\n>> +++ b/merge-ort.c\n>> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n>>                         const char *reason = _(\"content\");\n>>                         if (ci->filemask == 6)\n>>                                 reason = _(\"add/add\");\n>> -                       if (S_ISGITLINK(merged_file.mode))\n>> +                       if (S_ISGITLINK(merged_file.mode)) {\n>>                                 reason = _(\"submodule\");\n>> +                               if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n>> +                                       oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n>> +                                       string_list_append(opt->conflicted_submodule_paths, path);\n>> +                               }\n>> +                       }\n>>                         path_msg(opt, path, 0,\n>>                                  _(\"CONFLICT (%s): Merge conflict in %s\"),\n>>                                  reason, path);\n>> diff --git a/merge-recursive.c b/merge-recursive.c\n>> index fd1bbde061..ff7cdbefe9 100644\n>> --- a/merge-recursive.c\n>> +++ b/merge-recursive.c\n>> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>>         }\n>>\n>>         if (!mfi->clean) {\n>> -               if (S_ISGITLINK(mfi->blob.mode))\n>> +               if (S_ISGITLINK(mfi->blob.mode)) {\n>>                         reason = _(\"submodule\");\n>> +                       if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n>> +                               oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n>> +                               string_list_append(opt->conflicted_submodule_paths, path);\n>> +                       }\n>> +               }\n>>                 output(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n>>                                 reason, path);\n>>                 if (ci && !df_conflict_remains)\n> \n> Nice that the changes needed to both the ort and recursive strategies\n> are so localized.  :-)\n> \n>> diff --git a/merge-recursive.h b/merge-recursive.h\n>> index b88000e3c2..5d267e7a43 100644\n>> --- a/merge-recursive.h\n>> +++ b/merge-recursive.h\n>> @@ -51,6 +51,10 @@ struct merge_options {\n>>\n>>         /* internal fields used by the implementation */\n>>         struct merge_options_internal *priv;\n>> +\n>> +       /* fields that hold submodule conflict information */\n>> +       struct oid_array *conflicted_submodule_oids;\n>> +       struct string_list *conflicted_submodule_paths;\n>>  };\n> \n> Make sense.\n> \n>>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n>> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n>> index 178413c22f..5b384dedc1 100755\n>> --- a/t/t6437-submodule-merge.sh\n>> +++ b/t/t6437-submodule-merge.sh\n>> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>>                 test_must_fail git merge c 2> actual\n>>           fi &&\n>>          grep $(cat expect) actual > /dev/null &&\n>> +        test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n>>          git reset --hard)\n>>  '\n>>\n\nIn this test, an existing merge does exist, and is suggested to the user with [message 2] above.\nSo telling users to do a new merge in the submodule is maybe to what we want.\n\n>> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>>          fi &&\n>>         grep $(cat expect1) actual > /dev/null &&\n>>         grep $(cat expect2) actual > /dev/null &&\n>> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n>>         git reset --hard)\n>>  '\n\nHere, 2 existing merges exist, and they are presented to the users with [message 3] above.\nSo again we might not want to tell users to do a new merge.\n\n>>\n>> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>>         git commit -a -m \"f\" &&\n>>\n>>         git checkout -b test-backward e &&\n>> -       test_must_fail git merge f)\n>> +       test_must_fail git merge f >actual &&\n>> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n> \n> test_i18ngrep is apparently on the way out:\n> \n>     $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh\n>     # Wrapper for grep which used to be used for\n>     # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other\n>     # in-flight changes. Should not be used and will be removed soon.\n>     test_i18ngrep () {\n> \n> I think you just want to use grep instead here for each of these hunks.\n> \n\nHere, one side regresses the submodule commit and the other side advances it.\nIn this case, I really think the right resolution is to choose one side or \nthe other, and not suggest to do a merge at all. So that means that we might\nwant to tweak the advice we give based on the type of submodule conflict...\n\nAlso, maybe we would want a new test that reproduces exactly the conditions of \n[message 1], i.e. no existing merge exists in the submodule.\n\nThanks a lot for wanting to improve the submodule UX!\n\nCheers,\n\nPhilippe.\n"},{"id":"457078","messageId":"CABPp-BEmVJu1w+2ECwyPzWe8cSHqJZis6AP10OsKSoDJ2W7iqA@mail.gmail.com","threadId":"57963","inReplyTo":"ed62e9ad-536f-3ab8-2972-17c7f8996617@gmail.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-12T06:56:40Z","receivedAt":"2022-06-12T06:57:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Philippe,\n\nOn Sat, Jun 11, 2022 at 10:08 AM Philippe Blain\n<levraiphilippeblain@gmail.com> wrote:\n>\n> Hi Calvin and Elijah,\n>\n> Le 2022-06-11 à 00:53, Elijah Newren a écrit :\n> > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote:\n> >>\n> >> When attempting to do a non-fast-forward merge in a project with\n> >> conflicts in the submodules, the merge fails and git prints the\n> >> following error:\n>\n> I think we would want to be slightly more precise here, as\n> \"conflicts in the submodules\" could be understood to mean:\n>\n> 1) conflicting submodule pointers in the superproject being merged\n> 2) 1. + also content conflicts in the submodule merge\n>\n> Here we are just talking about 1.\n\nIsn't that already addressed by Calvin's wording \"recursive merging\nwith submodules is currently not supported...\"?  Or are you concerned\nthat this wording will be separated from the short conflict line?\n\n> Also, the merge does not automatically fail, it only fails if\n> a fast-forward *of the submodule pointer* is not possible, which\n> might be what you meant above; but to me \"a non-fast-forward merge in a project\n> with conflict in the submodules\" read like the non-fast-forwardness being talked about\n> was in the superproject, not in the submodule(s).\n>\n> [message 0]\n> >>\n> >> Failed to merge submodule <submodules>\n> >> CONFLICT (submodule):Merge conflict in <submodule>\n> >> Automatic merge failed; fix conflicts and then commit the result.\n>\n>    Aside: the first <submodules> should be singular.\n>\n> This is indeed the output you get with the ort strategy if no existing merge commit\n> exist in the submodule repository that merges the submodule pointers recorded\n> in the superproject branches being merged. With the older \"recursive\" strategy,\n> this message is:\n>\n> [message 1]\n> Failed to merge submodule sub (merge following commits not found)\n> Auto-merging sub\n> CONFLICT (submodule): Merge conflict in sub\n> Automatic merge failed; fix conflicts and then commit the result\n>\n> c73cda76b1 (merge-ort: copy and adapt merge_submodule()\n> from merge-recursive.c, 2021-01-01) does not mention why that error message\n> was changed, but perhaps it is just because it is slightly confusing\n> to the user (they might not be expecting Git to look for an existing\n> merge and so they don't know what merge the message is talking about).\n\nThe text in parentheses from merge-recursive.c didn't parse for me.\nPerhaps if it instead read \"(failed to find merge commit in submodule\nto use as merge resolution)\" or something like that then I might have\nkept it.  As it was, I found it really hard to parse (\"Why are there\nno commits after the instruction to 'merge following commits'\"?  Is\n\"merge\" a noun or a verb as used in the parenthetical comment? etc.).\nThe text could have been improved, but a really easy way to improve it\nwas just to remove it altogether.  Removing it was an easy way to\nimprove it, so I did that.\n\nAlso, a little rant about merge_submodule()....that function\noriginally had multiple suboptimal error messages, used to write\ndirectly to stdout instead of using the output() function to respect\nverbosity, didn't internationalize messages, didn't consider\nrecursiveness in the resolution, has verbose output that only makes\nsense when updating the working tree and index, etc.  It was quite a\nmess.  Some of that got cleaned up in merge-recursive.c.  Some only\ngot cleaned up with the move to merge-ort.c.  Some of it sadly still\npersists (as mentioned elsewhere in this email).\n\n> Maybe something like \"failed to find existing commit merging <hash1> and <hash2>\"\n> would be clearer...\n\nYes, that would be much clearer.  Can I tweak the suggestion to\n\"failed to find an existing commit in submodule merging <hash1> and\n<hash2>\"?\n\nHowever, while it's work in a related area (merging of submodules), I\nthink this suggestion is separate from what Calvin is trying to fix\nand doesn't need to hold up his patch.\n\n> >>\n> >> Git is left in a conflicted state, which requires the user to:\n> >>  1. merge submodules\n> >>  2. add submodules changes to the superproject\n> >>  3. merge superproject\n> >\n> > I think we may need to tweak these steps a bit:\n> >\n> >    1. merge submodules OR update submodules to an already existing\n> > commit that reflects the merge (i.e. as submodules they may well be\n> > independently actively developed.  Someone may have already merged the\n> > appropriate branch/commit and the already extant merges should be used\n> > in preference to creating new ones.)\n> >    2. <just as you said>\n> >    3. FINISH merging the superproject (i.e. don't redo the merge)\n> >\n> > I might be off on step 1; I have only used submodules extremely\n> > lightly and usually only for a limited time, so I'm not really sure\n> > what the expected workflow is.  I could also imagine it potentially\n> > being repository-dependent whether you would want to merge or select\n> > an appropriate commit to update to.\n>\n> I agree with Elijah here, the submodule conflcit resolution might be to:\n>\n> 1) just choose one of the existing submodule commits on either side of\n> the superproject branches being merged\n> 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first)\n> 3) create such a merge commit (what you are talking about here)\n>\n> I also agree that it is highly repository- and workflow-dependent what\n> the \"right\" resolution is.\n>\n> Note that the code does try to find an existing merge commit in the submodule\n> repository, in this case the error message is different. If such a merge commit\n> exists:\n>\n>     [message 2]\n>     Failed to merge submodule sub, but a possible merge resolution exists:\n>         aafcfa2 Merge branch 'sub-c' into sub-d\n>\n>\n>     If this is correct simply add it to the index for example\n>     by using:\n>\n>       git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 \"sub\"\n>\n>     which will accept this suggestion.\n>\n>     CONFLICT (submodule): Merge conflict in sub\n>     Automatic merge failed; fix conflicts and then commit the result.\n>\n> if multiple merge exist:\n>\n>     [message 3]\n>     Failed to merge submodule sub, but multiple possible merges exist:\n>         2729a0c Merge branch 'sub-c' into ambiguous\n>         aafcfa2 Merge branch 'sub-c' into sub-d\n>\n>     CONFLICT (submodule): Merge conflict in sub\n>     Automatic merge failed; fix conflicts and then commit the result.\n>\n> Another aside, I really don't think we should instruct users to run\n> plumbing like 'git update-index --cacheinfo' , they should just cd into\n> the submodule and checkout the merge commit!\n\n+1\n\n> >\n> >> These steps are non-obvious for newer submodule users to figure out\n> >> based on the error message and neither `git submodule status` nor `git\n> >> status` provide any useful pointers.\n> >>\n> >> Update error message to the following when attempting to do a\n> >> non-fast-forward merge in a project with conflicts in the submodules.\n> >\n> > Make sense.\n>\n> I agree that more guidance is a very nice addition.\n>\n> Regarding 'git status' output, it is downright confusing, since it says:\n>\n> Unmerged paths:\n>   (use \"git add <file>...\" to mark resolution)\n>         both modified:   sub\n>\n> which is not at all what you want to do most of the time (that would\n> just stage whatever the currently checked out commit in the submodule is\n> at the moment!)\n\nSure, but this isn't anything special about submodules.  You don't\nwant to \"git add $path\" if you haven't resolved the conflicts in $path\nfirst.  That's advice that is true regardless of whether it's a\nregular text file that was present on both sides of the merge, in\nwhich case there will be conflict markers to remind the user to\nresolve conflicts first, or whether it's something else.  And there\nare a lot of something elses without conflict markers: a submodule, a\nbinary file, a symlink, path from file/directory conflict, something\nwith conflicting modes, a file with any of a variety of rename-related\npath conflicts, maybe others.\n\nPerhaps you just want the advice changed to\n\n    (use \"git add <file>...\" after resolving conflicts for the\nspecified path(s))\n\n?  It's a bit of a mouthful, but I guess it addresses your concern.\nIt's another possible improvement that belongs outside Calvin's patch\nso it can be discussed separately.  :-)\n\n>> The error message is based off of what would happen when `merge\n> >> --recurse-submodules` is eventually supported\n> >>\n> >> Failed to merge submodule <submodule>\n> >> CONFLICT (submodule): Merge conflict in <submodule>\n> >> Automatic merge failed; recursive merging with submodules is currently\n> >> not supported. To manually complete the merge:\n> >>  - go to submodule (<submodule>), and merge commit <commit>\n> >>  - come back to superproject, and `git add <submodule>` to record the above merge\n> >>  - resolve any other conflicts in the superproject\n> >>  - commit the resulting index in the superproject\n> >\n> > Ah, I see you've fixed step 3 here; that's good.\n> >\n> > However, these steps miss out on the merge-or-update submodule\n> > possibility...and since you mention these steps are potentially the\n> > basis for some future work, I think it's worth calling that out again.\n> > I'm slightly worried that the 'update' part of merge-or-update may\n> > throw a wrench in the plans for `merge --recurse-submodules`.\n> >\n>\n> Slightly off topic here, but for me the most important improvement that\n> 'git merge --recurse-submodules' would bring is when there is *no submodule\n> conflicts*, i.e. one side fast-forwards the submodule and the other side\n> does not touch it, since in that case the worktree of the submodule *is not updated*\n> by the current code, which is one of the most confusing aspect of using submodules\n> for new users (\"why is \"git status\" and \"git diff\" not clean if \"git merge\"\n> was fast-forward ?!?\"), and the same is true (maybe more even so) for 'git rebase'.\n\nDo you really mean \"i.e.\" here or did you mean \"e.g.\"?  I think the\nexample you give is just one where a submodule can be cleanly merged\nand then doesn't get updated.  Another is when an already existing\nsubmodule merge commit can be used as the merge resolution for the\nsubmodule.  The merging code simply doesn't update the working tree\n(or indexes) of submodules regardless of whether it updates the\nsubmodule pointers.\n\nTaking a quick glance, I think it might turn out to be trivial to fix\nthis in merge-ort.c.  I _think_ simply setting\n   config_update_recurse_submodules = RECURSE_SUBMODULES_ON\nin merge-ort.c:checkout() anywhere before the call to unpack_trees()\nmight be enough.  Want to try it?\n\nThe fix in merge-recursive is probably much more involved, and likely\nnot worth the effort (there's a related comment in the code in\nupdate_file_flags() about this; look for \"submodule\").\n\n> > (Also, continuing on the `merge --recurse-submodules` talent but\n> > discussing a different aspect of it, I'm curious if you need to add\n> > extra dirty-worktree/dirty-index checks for each submodule at the\n> > start of a merge, whether you need to try to lock N indexes before\n> > starting, and what other extra details are necessary.  But those are\n> > probably questions to address whenever work on the future series to\n> > implement this option is underway.)\n> >\n> >> Changes since v1:\n> >>  - Removed advice to abort merge\n> >>  - Error message updated to contain more commit/submodule information\n> >>\n> >> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> >>\n> >> ---\n> >>  builtin/merge.c            | 23 ++++++++++++++++++++++-\n> >>  merge-ort.c                |  7 ++++++-\n> >>  merge-recursive.c          |  7 ++++++-\n> >>  merge-recursive.h          |  4 ++++\n> >>  t/t6437-submodule-merge.sh |  5 ++++-\n> >>  5 files changed, 42 insertions(+), 4 deletions(-)\n> >\n> > So you're modifying the \"git merge\" porcelain level (builtin/merge.c),\n> > the two merges strategies, their common header, and adding some tests.\n> > No other porcelains are modified...\n> >\n> >> diff --git a/builtin/merge.c b/builtin/merge.c\n> >> index f178f5a3ee..7e2deea7fb 100644\n> >> --- a/builtin/merge.c\n> >> +++ b/builtin/merge.c\n> >> @@ -88,6 +88,8 @@ static const char *sign_commit;\n> >>  static int autostash;\n> >>  static int no_verify;\n> >>  static char *into_name;\n> >> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n> >> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n> >>\n> >>  static struct strategy all_strategy[] = {\n> >>         { \"recursive\",  NO_TRIVIAL },\n> >> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n> >>                 }\n> >>\n> >>                 init_merge_options(&o, the_repository);\n> >> +               o.conflicted_submodule_oids = &conflicted_submodule_oids;\n> >> +               o.conflicted_submodule_paths = &conflicted_submodule_paths;\n> >>                 if (!strcmp(strategy, \"subtree\"))\n> >>                         o.subtree_shift = \"\";\n> >>\n> >> @@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n> >>         strbuf_release(&msgbuf);\n> >>         fclose(fp);\n> >>         repo_rerere(the_repository, allow_rerere_auto);\n> >> -       printf(_(\"Automatic merge failed; \"\n> >> +       if (conflicted_submodule_oids.nr > 0) {\n> >> +               int i;\n> >> +               printf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n> >> +                       \"not supported. To manually complete the merge:\\n\"));\n> >> +               for (i = 0; i < conflicted_submodule_oids.nr; i++) {\n> >> +                       printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n> >> +                               conflicted_submodule_paths.items[i].string,\n> >> +                               oid_to_hex(&conflicted_submodule_oids.oid[i]));\n> >> +               }\n> >> +               printf(_(\" - come back to superproject, and `git add\"));\n> >> +               for (i = 0; i < conflicted_submodule_paths.nr; i++)\n> >> +                       printf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n> >> +               printf(_(\"` to record the above merge \\n\"\n> >> +               \" - resolve any other conflicts in the superproject\\n\"\n> >> +               \" - commit the resulting index in the superproject\\n\"));\n> >> +       } else {\n> >> +               printf(_(\"Automatic merge failed; \"\n> >>                         \"fix conflicts and then commit the result.\\n\"));\n> >> +       }\n> >>         return 1;\n> >>  }\n> >\n> > This is kind of nice.  I was worried you were going to embed these\n> > messages in the merge strategies, which could cause problems for other\n> > users of the merge strategies such as the --remerge-diff options to\n> > git log and git show (your new messages would be unwanted noise or\n> > even cause confusion there), and to the merge-tree work.  In fact, a\n> > current submodule-merging message (search for \"--cacheinfo\") that is\n> > potentially similar to what you are adding here but which was added at\n> > the merge strategy level already feels highly problematic to me.  I've\n> > been considering nuking it from the codebase for some time because of\n> > those issues, though I guess just moving it out elsewhere may also\n> > work.\n> >\n>\n> Yes, this is the message I copied above. I agree that if we can tweak this\n> advice to instead mention 'git checkout' and add it to the message\n> that Calvin is adding in this series, it would make for a really better\n> UX.\n\nThis message shouldn't be tweaked; it should be expunged.  It does not\nbelong in the low-level code where it is.  Now, if someone wants to\nmove the message somewhere else so that it only is displayed when the\nworking tree and index are updated, and they want to tweak it at the\nsame time to avoid the ugly suggestion to use low-level plumbing,\nthat'd be great.  But neither of those things need to hold up Calvin's\nseries.  (I think you're providing lots of good points of what can be\nimproved, Philippe, I just want to be clear that I think they are\nrelated but separate improvements.)\n\n\n> > However, this implementation does have a drawback: these messages\n> > won't appear for rebases, cherry-picks, reverts, attempted unstashing\n> > (git stash apply/pop), or other actions unless you update the relevant\n> > porcelains for those as well.\n> >\n> > A possible alternative here would be to move it to the level of\n> > merge-recursive and merge-ort that is only called when the working\n> > tree and index are updated.  For example, placing it in\n> > merge_finalize() in merge-recursive.c and merge_switch_to_result() in\n> > merge-ort.c -- next to the diff_warn_rename_limit() call in each case.\n> > However, I'm also fine with keeping it at the porcelain level, it just\n> > may need to be in a function that is called from several porcelains\n> > that way.\n>\n> I think moving it to merge_finalize / merge_switch_to_result is indeed a\n> good suggestion, then we might be improving the UX across the board and not just\n> for 'git merge'.\n>\n> >\n> >> diff --git a/merge-ort.c b/merge-ort.c\n> >> index 0d3f42592f..c86ee11614 100644\n> >> --- a/merge-ort.c\n> >> +++ b/merge-ort.c\n> >> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n> >>                         const char *reason = _(\"content\");\n> >>                         if (ci->filemask == 6)\n> >>                                 reason = _(\"add/add\");\n> >> -                       if (S_ISGITLINK(merged_file.mode))\n> >> +                       if (S_ISGITLINK(merged_file.mode)) {\n> >>                                 reason = _(\"submodule\");\n> >> +                               if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> >> +                                       oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n> >> +                                       string_list_append(opt->conflicted_submodule_paths, path);\n> >> +                               }\n> >> +                       }\n> >>                         path_msg(opt, path, 0,\n> >>                                  _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >>                                  reason, path);\n> >> diff --git a/merge-recursive.c b/merge-recursive.c\n> >> index fd1bbde061..ff7cdbefe9 100644\n> >> --- a/merge-recursive.c\n> >> +++ b/merge-recursive.c\n> >> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n> >>         }\n> >>\n> >>         if (!mfi->clean) {\n> >> -               if (S_ISGITLINK(mfi->blob.mode))\n> >> +               if (S_ISGITLINK(mfi->blob.mode)) {\n> >>                         reason = _(\"submodule\");\n> >> +                       if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> >> +                               oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n> >> +                               string_list_append(opt->conflicted_submodule_paths, path);\n> >> +                       }\n> >> +               }\n> >>                 output(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >>                                 reason, path);\n> >>                 if (ci && !df_conflict_remains)\n> >\n> > Nice that the changes needed to both the ort and recursive strategies\n> > are so localized.  :-)\n> >\n> >> diff --git a/merge-recursive.h b/merge-recursive.h\n> >> index b88000e3c2..5d267e7a43 100644\n> >> --- a/merge-recursive.h\n> >> +++ b/merge-recursive.h\n> >> @@ -51,6 +51,10 @@ struct merge_options {\n> >>\n> >>         /* internal fields used by the implementation */\n> >>         struct merge_options_internal *priv;\n> >> +\n> >> +       /* fields that hold submodule conflict information */\n> >> +       struct oid_array *conflicted_submodule_oids;\n> >> +       struct string_list *conflicted_submodule_paths;\n> >>  };\n> >\n> > Make sense.\n> >\n> >>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> >> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> >> index 178413c22f..5b384dedc1 100755\n> >> --- a/t/t6437-submodule-merge.sh\n> >> +++ b/t/t6437-submodule-merge.sh\n> >> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n> >>                 test_must_fail git merge c 2> actual\n> >>           fi &&\n> >>          grep $(cat expect) actual > /dev/null &&\n> >> +        test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >>          git reset --hard)\n> >>  '\n> >>\n>\n> In this test, an existing merge does exist, and is suggested to the user with [message 2] above.\n> So telling users to do a new merge in the submodule is maybe to what we want.\n>\n> >> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n> >>          fi &&\n> >>         grep $(cat expect1) actual > /dev/null &&\n> >>         grep $(cat expect2) actual > /dev/null &&\n> >> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >>         git reset --hard)\n> >>  '\n>\n> Here, 2 existing merges exist, and they are presented to the users with [message 3] above.\n> So again we might not want to tell users to do a new merge.\n>\n> >>\n> >> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n> >>         git commit -a -m \"f\" &&\n> >>\n> >>         git checkout -b test-backward e &&\n> >> -       test_must_fail git merge f)\n> >> +       test_must_fail git merge f >actual &&\n> >> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n> >\n> > test_i18ngrep is apparently on the way out:\n> >\n> >     $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh\n> >     # Wrapper for grep which used to be used for\n> >     # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other\n> >     # in-flight changes. Should not be used and will be removed soon.\n> >     test_i18ngrep () {\n> >\n> > I think you just want to use grep instead here for each of these hunks.\n> >\n>\n> Here, one side regresses the submodule commit and the other side advances it.\n> In this case, I really think the right resolution is to choose one side or\n> the other, and not suggest to do a merge at all. So that means that we might\n> want to tweak the advice we give based on the type of submodule conflict...\n>\n> Also, maybe we would want a new test that reproduces exactly the conditions of\n> [message 1], i.e. no existing merge exists in the submodule.\n>\n> Thanks a lot for wanting to improve the submodule UX!\n>\n> Cheers,\n>\n> Philippe.\n"},{"id":"457092","messageId":"xmqqk09l8no3.fsf@gitster.g","threadId":"57963","inReplyTo":"CABPp-BHe7wdd3LMYtZ83ZgapvUxzQYcK-3=sdMAD5Ozs4YmKyw@mail.gmail.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-12T23:30:52Z","receivedAt":"2022-06-12T23:31:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> Git is left in a conflicted state, which requires the user to:\n>>  1. merge submodules\n>>  2. add submodules changes to the superproject\n>>  3. merge superproject\n>\n> I think we may need to tweak these steps a bit:\n>\n>    1. merge submodules OR update submodules to an already existing\n> commit that reflects the merge (i.e. as submodules they may well be\n> independently actively developed.  Someone may have already merged the\n> appropriate branch/commit and the already extant merges should be used\n> in preference to creating new ones.)\n\nThat is a very good point.\n\nI suspect we should encourage users to find an existing merge, and\npossibly even discourage them from creating a new one on their own.\nThere may not be much point in creating the same merge of identical\nparent commits with the same result that only has different\nmetadata.\n\nIt may be tempting to argue that an existing merge in a submodule\nand the merge you are creating now are made in different context\nbecause the superproject merges that necessitate thse submodule\nmerges are different, but I doubt that is a healthy argument.  A\ncommit in the submodule should be able to explain its reason to\nexist on its own---after all, the superproject may know which\nsubmodules are used for what purpose, but a submodule does not have\nto know where it is used, and that is how it is made more reusable.\n\n>    2. <just as you said>\n>    3. FINISH merging the superproject (i.e. don't redo the merge)\n\nYes, exactly.  FWIW, an earlier draft was telling users to abort the\nmerge in the first step, and its review corrected it.  1 and 2 are\nto be done in the context of the interrupted superproject merge, so\nits logical consequence is to conclude the merge at step 3.\n"},{"id":"457095","messageId":"CAFySSZDtHbFV92bLPrE4U7LCX+232zNVETZaOb2qLvyQmucEnQ@mail.gmail.com","threadId":"57963","inReplyTo":"CABPp-BHe7wdd3LMYtZ83ZgapvUxzQYcK-3=sdMAD5Ozs4YmKyw@mail.gmail.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-13T01:54:15Z","receivedAt":"2022-06-13T01:54:33Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> I think we may need to tweak these steps a bit:\n\n>    1. merge submodules OR update submodules to an already existing\n> commit that reflects the merge (i.e. as submodules they may well be\n> independently actively developed.  Someone may have already merged the\n> appropriate branch/commit and the already extant merges should be used\n> in preference to creating new ones.)\n>    2. <just as you said>\n>    3. FINISH merging the superproject (i.e. don't redo the merge)\n\n> I might be off on step 1; I have only used submodules extremely\n> lightly and usually only for a limited time, so I'm not really sure\n> what the expected workflow is.  I could also imagine it potentially\n> being repository-dependent whether you would want to merge or select\n> an appropriate commit to update to.\n\nYou are correct that either merging or selecting an appropriate commit to\nupdate to (if it exists) are the two ways to fix a submodule conflict. As\nPhillippe mentions below, if merge detects a possible merge resolution,\nthat merge resolution is printed out as in the suggestion. I wonder if it's\neasy to pull out the exact reason for why the submodule merge failed. If\nthat is the case, then I think changing the first step to something like:\n\n(If no merge resolution already exists, the suggestion stays the same)\n  1. - go to submodule (<submodule>) and merge commit <commit>\n(If a merge resolution exists)\n  1. - go to submodule (<submodule>). Either update the submodule\n        to one of the possible merge commits or merge commit <commit>\n\nIf it is not easy to pull out the merge fail reason, then I think we can\ngeneralize the first step to:\n\n1. - go to submodule (<submodule>). Merge commit <commit> or if\n      a possible merge resolution exists, then you have the option to\n      update the submodule to one of the merge commits\n\n> However, this implementation does have a drawback: these messages\n> won't appear for rebases, cherry-picks, reverts, attempted unstashing\n> (git stash apply/pop), or other actions unless you update the relevant\n> porcelains for those as well.\n\n> A possible alternative here would be to move it to the level of\n> merge-recursive and merge-ort that is only called when the working\n> tree and index are updated.  For example, placing it in\n> merge_finalize() in merge-recursive.c and merge_switch_to_result() in\n> merge-ort.c -- next to the diff_warn_rename_limit() call in each case.\n> However, I'm also fine with keeping it at the porcelain level, it just\n> may need to be in a function that is called from several porcelains\n> that way.\n\nYou make a good point here. I'm inclined to keep it at the porcelain level\nsince this is a temporary holdover until `merge --recurse-submodules` is\nimplemented.\n\n> test_i18ngrep is apparently on the way out:\n\nack\n\nOn Fri, Jun 10, 2022 at 9:53 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote:\n> >\n> > When attempting to do a non-fast-forward merge in a project with\n> > conflicts in the submodules, the merge fails and git prints the\n> > following error:\n> >\n> > Failed to merge submodule <submodules>\n> > CONFLICT (submodule):Merge conflict in <submodule>\n> > Automatic merge failed; fix conflicts and then commit the result.\n> >\n> > Git is left in a conflicted state, which requires the user to:\n> >  1. merge submodules\n> >  2. add submodules changes to the superproject\n> >  3. merge superproject\n>\n> I think we may need to tweak these steps a bit:\n>\n>    1. merge submodules OR update submodules to an already existing\n> commit that reflects the merge (i.e. as submodules they may well be\n> independently actively developed.  Someone may have already merged the\n> appropriate branch/commit and the already extant merges should be used\n> in preference to creating new ones.)\n>    2. <just as you said>\n>    3. FINISH merging the superproject (i.e. don't redo the merge)\n>\n> I might be off on step 1; I have only used submodules extremely\n> lightly and usually only for a limited time, so I'm not really sure\n> what the expected workflow is.  I could also imagine it potentially\n> being repository-dependent whether you would want to merge or select\n> an appropriate commit to update to.\n>\n> > These steps are non-obvious for newer submodule users to figure out\n> > based on the error message and neither `git submodule status` nor `git\n> > status` provide any useful pointers.\n> >\n> > Update error message to the following when attempting to do a\n> > non-fast-forward merge in a project with conflicts in the submodules.\n>\n> Make sense.\n>\n> > The error message is based off of what would happen when `merge\n> > --recurse-submodules` is eventually supported\n> >\n> > Failed to merge submodule <submodule>\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Automatic merge failed; recursive merging with submodules is currently\n> > not supported. To manually complete the merge:\n> >  - go to submodule (<submodule>), and merge commit <commit>\n> >  - come back to superproject, and `git add <submodule>` to record the above merge\n> >  - resolve any other conflicts in the superproject\n> >  - commit the resulting index in the superproject\n>\n> Ah, I see you've fixed step 3 here; that's good.\n>\n> However, these steps miss out on the merge-or-update submodule\n> possibility...and since you mention these steps are potentially the\n> basis for some future work, I think it's worth calling that out again.\n> I'm slightly worried that the 'update' part of merge-or-update may\n> throw a wrench in the plans for `merge --recurse-submodules`.\n>\n> (Also, continuing on the `merge --recurse-submodules` talent but\n> discussing a different aspect of it, I'm curious if you need to add\n> extra dirty-worktree/dirty-index checks for each submodule at the\n> start of a merge, whether you need to try to lock N indexes before\n> starting, and what other extra details are necessary.  But those are\n> probably questions to address whenever work on the future series to\n> implement this option is underway.)\n>\n> > Changes since v1:\n> >  - Removed advice to abort merge\n> >  - Error message updated to contain more commit/submodule information\n> >\n> > Signed-off-by: Calvin Wan <calvinwan@google.com>\n> >\n> > ---\n> >  builtin/merge.c            | 23 ++++++++++++++++++++++-\n> >  merge-ort.c                |  7 ++++++-\n> >  merge-recursive.c          |  7 ++++++-\n> >  merge-recursive.h          |  4 ++++\n> >  t/t6437-submodule-merge.sh |  5 ++++-\n> >  5 files changed, 42 insertions(+), 4 deletions(-)\n>\n> So you're modifying the \"git merge\" porcelain level (builtin/merge.c),\n> the two merges strategies, their common header, and adding some tests.\n> No other porcelains are modified...\n>\n> > diff --git a/builtin/merge.c b/builtin/merge.c\n> > index f178f5a3ee..7e2deea7fb 100644\n> > --- a/builtin/merge.c\n> > +++ b/builtin/merge.c\n> > @@ -88,6 +88,8 @@ static const char *sign_commit;\n> >  static int autostash;\n> >  static int no_verify;\n> >  static char *into_name;\n> > +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n> > +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n> >\n> >  static struct strategy all_strategy[] = {\n> >         { \"recursive\",  NO_TRIVIAL },\n> > @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n> >                 }\n> >\n> >                 init_merge_options(&o, the_repository);\n> > +               o.conflicted_submodule_oids = &conflicted_submodule_oids;\n> > +               o.conflicted_submodule_paths = &conflicted_submodule_paths;\n> >                 if (!strcmp(strategy, \"subtree\"))\n> >                         o.subtree_shift = \"\";\n> >\n> > @@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n> >         strbuf_release(&msgbuf);\n> >         fclose(fp);\n> >         repo_rerere(the_repository, allow_rerere_auto);\n> > -       printf(_(\"Automatic merge failed; \"\n> > +       if (conflicted_submodule_oids.nr > 0) {\n> > +               int i;\n> > +               printf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n> > +                       \"not supported. To manually complete the merge:\\n\"));\n> > +               for (i = 0; i < conflicted_submodule_oids.nr; i++) {\n> > +                       printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n> > +                               conflicted_submodule_paths.items[i].string,\n> > +                               oid_to_hex(&conflicted_submodule_oids.oid[i]));\n> > +               }\n> > +               printf(_(\" - come back to superproject, and `git add\"));\n> > +               for (i = 0; i < conflicted_submodule_paths.nr; i++)\n> > +                       printf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n> > +               printf(_(\"` to record the above merge \\n\"\n> > +               \" - resolve any other conflicts in the superproject\\n\"\n> > +               \" - commit the resulting index in the superproject\\n\"));\n> > +       } else {\n> > +               printf(_(\"Automatic merge failed; \"\n> >                         \"fix conflicts and then commit the result.\\n\"));\n> > +       }\n> >         return 1;\n> >  }\n>\n> This is kind of nice.  I was worried you were going to embed these\n> messages in the merge strategies, which could cause problems for other\n> users of the merge strategies such as the --remerge-diff options to\n> git log and git show (your new messages would be unwanted noise or\n> even cause confusion there), and to the merge-tree work.  In fact, a\n> current submodule-merging message (search for \"--cacheinfo\") that is\n> potentially similar to what you are adding here but which was added at\n> the merge strategy level already feels highly problematic to me.  I've\n> been considering nuking it from the codebase for some time because of\n> those issues, though I guess just moving it out elsewhere may also\n> work.\n>\n> However, this implementation does have a drawback: these messages\n> won't appear for rebases, cherry-picks, reverts, attempted unstashing\n> (git stash apply/pop), or other actions unless you update the relevant\n> porcelains for those as well.\n>\n> A possible alternative here would be to move it to the level of\n> merge-recursive and merge-ort that is only called when the working\n> tree and index are updated.  For example, placing it in\n> merge_finalize() in merge-recursive.c and merge_switch_to_result() in\n> merge-ort.c -- next to the diff_warn_rename_limit() call in each case.\n> However, I'm also fine with keeping it at the porcelain level, it just\n> may need to be in a function that is called from several porcelains\n> that way.\n>\n> > diff --git a/merge-ort.c b/merge-ort.c\n> > index 0d3f42592f..c86ee11614 100644\n> > --- a/merge-ort.c\n> > +++ b/merge-ort.c\n> > @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n> >                         const char *reason = _(\"content\");\n> >                         if (ci->filemask == 6)\n> >                                 reason = _(\"add/add\");\n> > -                       if (S_ISGITLINK(merged_file.mode))\n> > +                       if (S_ISGITLINK(merged_file.mode)) {\n> >                                 reason = _(\"submodule\");\n> > +                               if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> > +                                       oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n> > +                                       string_list_append(opt->conflicted_submodule_paths, path);\n> > +                               }\n> > +                       }\n> >                         path_msg(opt, path, 0,\n> >                                  _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >                                  reason, path);\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n> > index fd1bbde061..ff7cdbefe9 100644\n> > --- a/merge-recursive.c\n> > +++ b/merge-recursive.c\n> > @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n> >         }\n> >\n> >         if (!mfi->clean) {\n> > -               if (S_ISGITLINK(mfi->blob.mode))\n> > +               if (S_ISGITLINK(mfi->blob.mode)) {\n> >                         reason = _(\"submodule\");\n> > +                       if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> > +                               oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n> > +                               string_list_append(opt->conflicted_submodule_paths, path);\n> > +                       }\n> > +               }\n> >                 output(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >                                 reason, path);\n> >                 if (ci && !df_conflict_remains)\n>\n> Nice that the changes needed to both the ort and recursive strategies\n> are so localized.  :-)\n>\n> > diff --git a/merge-recursive.h b/merge-recursive.h\n> > index b88000e3c2..5d267e7a43 100644\n> > --- a/merge-recursive.h\n> > +++ b/merge-recursive.h\n> > @@ -51,6 +51,10 @@ struct merge_options {\n> >\n> >         /* internal fields used by the implementation */\n> >         struct merge_options_internal *priv;\n> > +\n> > +       /* fields that hold submodule conflict information */\n> > +       struct oid_array *conflicted_submodule_oids;\n> > +       struct string_list *conflicted_submodule_paths;\n> >  };\n>\n> Make sense.\n>\n> >  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> > index 178413c22f..5b384dedc1 100755\n> > --- a/t/t6437-submodule-merge.sh\n> > +++ b/t/t6437-submodule-merge.sh\n> > @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n> >                 test_must_fail git merge c 2> actual\n> >           fi &&\n> >          grep $(cat expect) actual > /dev/null &&\n> > +        test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >          git reset --hard)\n> >  '\n> >\n> > @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n> >          fi &&\n> >         grep $(cat expect1) actual > /dev/null &&\n> >         grep $(cat expect2) actual > /dev/null &&\n> > +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >         git reset --hard)\n> >  '\n> >\n> > @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n> >         git commit -a -m \"f\" &&\n> >\n> >         git checkout -b test-backward e &&\n> > -       test_must_fail git merge f)\n> > +       test_must_fail git merge f >actual &&\n> > +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n>\n> test_i18ngrep is apparently on the way out:\n>\n>     $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh\n>     # Wrapper for grep which used to be used for\n>     # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other\n>     # in-flight changes. Should not be used and will be removed soon.\n>     test_i18ngrep () {\n>\n> I think you just want to use grep instead here for each of these hunks.\n"},{"id":"457096","messageId":"CAFySSZDUwLYVhJc8tmEcYh2Q8ABvTkRfruzS2ZeG__SoWXa=cQ@mail.gmail.com","threadId":"57963","inReplyTo":"ed62e9ad-536f-3ab8-2972-17c7f8996617@gmail.com","subject":"Re: [PATCH v2] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-13T02:03:43Z","receivedAt":"2022-06-13T02:08:40Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> I think we would want to be slightly more precise here, as\n> \"conflicts in the submodules\" could be understood to mean:\n\n> 1) conflicting submodule pointers in the superproject being merged\n> 2) 1. + also content conflicts in the submodule merge\n\n> Here we are just talking about 1.\n\nWill clarify\n\n> Also, the merge does not automatically fail, it only fails if\n> a fast-forward *of the submodule pointer* is not possible, which\n> might be what you meant above; but to me \"a non-fast-forward merge in a project\n> with conflict in the submodules\" read like the non-fast-forwardness being talked about\n> was in the superproject, not in the submodule(s).\n\nIt was what I meant above, but I do agree that I can reword this\nsection as well.\n\n> I agree with Elijah here, the submodule conflcit resolution might be to:\n\n> 1) just choose one of the existing submodule commits on either side of\n> the superproject branches being merged\n> 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first)\n> 3) create such a merge commit (what you are talking about here)\n\n> I also agree that it is highly repository- and workflow-dependent what\n> the \"right\" resolution is.\n\nI replied to Elijah's message with some ideas I had about updating my\nfirst step, but\nto summarize I do agree that my current message doesn't account for\nthe fact that a\nmerge resolution may already exist, and the test cases you mention\nserve as proof\nthat my current message is insufficient.\n\n> I think moving it to merge_finalize / merge_switch_to_result is indeed a\n> good suggestion, then we might be improving the UX across the board and not just\n> for 'git merge'.\n\nI'll spend some time looking into moving the suggestion code into\nthese functions. My\nmain concern is knowing which tests I would have to update to test\nthese changes.\n\nOn Sat, Jun 11, 2022 at 10:08 AM Philippe Blain\n<levraiphilippeblain@gmail.com> wrote:\n>\n> Hi Calvin and Elijah,\n>\n> Le 2022-06-11 à 00:53, Elijah Newren a écrit :\n> > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote:\n> >>\n> >> When attempting to do a non-fast-forward merge in a project with\n> >> conflicts in the submodules, the merge fails and git prints the\n> >> following error:\n>\n> I think we would want to be slightly more precise here, as\n> \"conflicts in the submodules\" could be understood to mean:\n>\n> 1) conflicting submodule pointers in the superproject being merged\n> 2) 1. + also content conflicts in the submodule merge\n>\n> Here we are just talking about 1.\n>\n> Also, the merge does not automatically fail, it only fails if\n> a fast-forward *of the submodule pointer* is not possible, which\n> might be what you meant above; but to me \"a non-fast-forward merge in a project\n> with conflict in the submodules\" read like the non-fast-forwardness being talked about\n> was in the superproject, not in the submodule(s).\n>\n> [message 0]\n> >>\n> >> Failed to merge submodule <submodules>\n> >> CONFLICT (submodule):Merge conflict in <submodule>\n> >> Automatic merge failed; fix conflicts and then commit the result.\n>\n>    Aside: the first <submodules> should be singular.\n>\n> This is indeed the output you get with the ort strategy if no existing merge commit\n> exist in the submodule repository that merges the submodule pointers recorded\n> in the superproject branches being merged. With the older \"recursive\" strategy,\n> this message is:\n>\n> [message 1]\n> Failed to merge submodule sub (merge following commits not found)\n> Auto-merging sub\n> CONFLICT (submodule): Merge conflict in sub\n> Automatic merge failed; fix conflicts and then commit the result\n>\n> c73cda76b1 (merge-ort: copy and adapt merge_submodule()\n> from merge-recursive.c, 2021-01-01) does not mention why that error message\n> was changed, but perhaps it is just because it is slightly confusing\n> to the user (they might not be expecting Git to look for an existing\n> merge and so they don't know what merge the message is talking about).\n>\n> Maybe something like \"failed to find existing commit merging <hash1> and <hash2>\"\n> would be clearer...\n>\n> >>\n> >> Git is left in a conflicted state, which requires the user to:\n> >>  1. merge submodules\n> >>  2. add submodules changes to the superproject\n> >>  3. merge superproject\n> >\n> > I think we may need to tweak these steps a bit:\n> >\n> >    1. merge submodules OR update submodules to an already existing\n> > commit that reflects the merge (i.e. as submodules they may well be\n> > independently actively developed.  Someone may have already merged the\n> > appropriate branch/commit and the already extant merges should be used\n> > in preference to creating new ones.)\n> >    2. <just as you said>\n> >    3. FINISH merging the superproject (i.e. don't redo the merge)\n> >\n> > I might be off on step 1; I have only used submodules extremely\n> > lightly and usually only for a limited time, so I'm not really sure\n> > what the expected workflow is.  I could also imagine it potentially\n> > being repository-dependent whether you would want to merge or select\n> > an appropriate commit to update to.\n>\n> I agree with Elijah here, the submodule conflcit resolution might be to:\n>\n> 1) just choose one of the existing submodule commits on either side of\n> the superproject branches being merged\n> 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first)\n> 3) create such a merge commit (what you are talking about here)\n>\n> I also agree that it is highly repository- and workflow-dependent what\n> the \"right\" resolution is.\n>\n> Note that the code does try to find an existing merge commit in the submodule\n> repository, in this case the error message is different. If such a merge commit\n> exists:\n>\n>     [message 2]\n>     Failed to merge submodule sub, but a possible merge resolution exists:\n>         aafcfa2 Merge branch 'sub-c' into sub-d\n>\n>\n>     If this is correct simply add it to the index for example\n>     by using:\n>\n>       git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 \"sub\"\n>\n>     which will accept this suggestion.\n>\n>     CONFLICT (submodule): Merge conflict in sub\n>     Automatic merge failed; fix conflicts and then commit the result.\n>\n> if multiple merge exist:\n>\n>     [message 3]\n>     Failed to merge submodule sub, but multiple possible merges exist:\n>         2729a0c Merge branch 'sub-c' into ambiguous\n>         aafcfa2 Merge branch 'sub-c' into sub-d\n>\n>     CONFLICT (submodule): Merge conflict in sub\n>     Automatic merge failed; fix conflicts and then commit the result.\n>\n> Another aside, I really don't think we should instruct users to run\n> plumbing like 'git update-index --cacheinfo' , they should just cd into\n> the submodule and checkout the merge commit!\n>\n> >\n> >> These steps are non-obvious for newer submodule users to figure out\n> >> based on the error message and neither `git submodule status` nor `git\n> >> status` provide any useful pointers.\n> >>\n> >> Update error message to the following when attempting to do a\n> >> non-fast-forward merge in a project with conflicts in the submodules.\n> >\n> > Make sense.\n>\n> I agree that more guidance is a very nice addition.\n>\n> Regarding 'git status' output, it is downright confusing, since it says:\n>\n> Unmerged paths:\n>   (use \"git add <file>...\" to mark resolution)\n>         both modified:   sub\n>\n> which is not at all what you want to do most of the time (that would\n> just stage whatever the currently checked out commit in the submodule is\n> at the moment!)\n>\n> >\n> >> The error message is based off of what would happen when `merge\n> >> --recurse-submodules` is eventually supported\n> >>\n> >> Failed to merge submodule <submodule>\n> >> CONFLICT (submodule): Merge conflict in <submodule>\n> >> Automatic merge failed; recursive merging with submodules is currently\n> >> not supported. To manually complete the merge:\n> >>  - go to submodule (<submodule>), and merge commit <commit>\n> >>  - come back to superproject, and `git add <submodule>` to record the above merge\n> >>  - resolve any other conflicts in the superproject\n> >>  - commit the resulting index in the superproject\n> >\n> > Ah, I see you've fixed step 3 here; that's good.\n> >\n> > However, these steps miss out on the merge-or-update submodule\n> > possibility...and since you mention these steps are potentially the\n> > basis for some future work, I think it's worth calling that out again.\n> > I'm slightly worried that the 'update' part of merge-or-update may\n> > throw a wrench in the plans for `merge --recurse-submodules`.\n> >\n>\n> Slightly off topic here, but for me the most important improvement that\n> 'git merge --recurse-submodules' would bring is when there is *no submodule\n> conflicts*, i.e. one side fast-forwards the submodule and the other side\n> does not touch it, since in that case the worktree of the submodule *is not updated*\n> by the current code, which is one of the most confusing aspect of using submodules\n> for new users (\"why is \"git status\" and \"git diff\" not clean if \"git merge\"\n> was fast-forward ?!?\"), and the same is true (maybe more even so) for 'git rebase'.\n>\n> > (Also, continuing on the `merge --recurse-submodules` talent but\n> > discussing a different aspect of it, I'm curious if you need to add\n> > extra dirty-worktree/dirty-index checks for each submodule at the\n> > start of a merge, whether you need to try to lock N indexes before\n> > starting, and what other extra details are necessary.  But those are\n> > probably questions to address whenever work on the future series to\n> > implement this option is underway.)\n> >\n> >> Changes since v1:\n> >>  - Removed advice to abort merge\n> >>  - Error message updated to contain more commit/submodule information\n> >>\n> >> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> >>\n> >> ---\n> >>  builtin/merge.c            | 23 ++++++++++++++++++++++-\n> >>  merge-ort.c                |  7 ++++++-\n> >>  merge-recursive.c          |  7 ++++++-\n> >>  merge-recursive.h          |  4 ++++\n> >>  t/t6437-submodule-merge.sh |  5 ++++-\n> >>  5 files changed, 42 insertions(+), 4 deletions(-)\n> >\n> > So you're modifying the \"git merge\" porcelain level (builtin/merge.c),\n> > the two merges strategies, their common header, and adding some tests.\n> > No other porcelains are modified...\n> >\n> >> diff --git a/builtin/merge.c b/builtin/merge.c\n> >> index f178f5a3ee..7e2deea7fb 100644\n> >> --- a/builtin/merge.c\n> >> +++ b/builtin/merge.c\n> >> @@ -88,6 +88,8 @@ static const char *sign_commit;\n> >>  static int autostash;\n> >>  static int no_verify;\n> >>  static char *into_name;\n> >> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT;\n> >> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP;\n> >>\n> >>  static struct strategy all_strategy[] = {\n> >>         { \"recursive\",  NO_TRIVIAL },\n> >> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n> >>                 }\n> >>\n> >>                 init_merge_options(&o, the_repository);\n> >> +               o.conflicted_submodule_oids = &conflicted_submodule_oids;\n> >> +               o.conflicted_submodule_paths = &conflicted_submodule_paths;\n> >>                 if (!strcmp(strategy, \"subtree\"))\n> >>                         o.subtree_shift = \"\";\n> >>\n> >> @@ -973,8 +977,25 @@ static int suggest_conflicts(void)\n> >>         strbuf_release(&msgbuf);\n> >>         fclose(fp);\n> >>         repo_rerere(the_repository, allow_rerere_auto);\n> >> -       printf(_(\"Automatic merge failed; \"\n> >> +       if (conflicted_submodule_oids.nr > 0) {\n> >> +               int i;\n> >> +               printf(_(\"Automatic merge failed; recursive merging with submodules is currently\\n\"\n> >> +                       \"not supported. To manually complete the merge:\\n\"));\n> >> +               for (i = 0; i < conflicted_submodule_oids.nr; i++) {\n> >> +                       printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n> >> +                               conflicted_submodule_paths.items[i].string,\n> >> +                               oid_to_hex(&conflicted_submodule_oids.oid[i]));\n> >> +               }\n> >> +               printf(_(\" - come back to superproject, and `git add\"));\n> >> +               for (i = 0; i < conflicted_submodule_paths.nr; i++)\n> >> +                       printf(_(\" %s\"), conflicted_submodule_paths.items[i].string);\n> >> +               printf(_(\"` to record the above merge \\n\"\n> >> +               \" - resolve any other conflicts in the superproject\\n\"\n> >> +               \" - commit the resulting index in the superproject\\n\"));\n> >> +       } else {\n> >> +               printf(_(\"Automatic merge failed; \"\n> >>                         \"fix conflicts and then commit the result.\\n\"));\n> >> +       }\n> >>         return 1;\n> >>  }\n> >\n> > This is kind of nice.  I was worried you were going to embed these\n> > messages in the merge strategies, which could cause problems for other\n> > users of the merge strategies such as the --remerge-diff options to\n> > git log and git show (your new messages would be unwanted noise or\n> > even cause confusion there), and to the merge-tree work.  In fact, a\n> > current submodule-merging message (search for \"--cacheinfo\") that is\n> > potentially similar to what you are adding here but which was added at\n> > the merge strategy level already feels highly problematic to me.  I've\n> > been considering nuking it from the codebase for some time because of\n> > those issues, though I guess just moving it out elsewhere may also\n> > work.\n> >\n>\n> Yes, this is the message I copied above. I agree that if we can tweak this\n> advice to instead mention 'git checkout' and add it to the message\n> that Calvin is adding in this series, it would make for a really better\n> UX.\n>\n> > However, this implementation does have a drawback: these messages\n> > won't appear for rebases, cherry-picks, reverts, attempted unstashing\n> > (git stash apply/pop), or other actions unless you update the relevant\n> > porcelains for those as well.\n> >\n> > A possible alternative here would be to move it to the level of\n> > merge-recursive and merge-ort that is only called when the working\n> > tree and index are updated.  For example, placing it in\n> > merge_finalize() in merge-recursive.c and merge_switch_to_result() in\n> > merge-ort.c -- next to the diff_warn_rename_limit() call in each case.\n> > However, I'm also fine with keeping it at the porcelain level, it just\n> > may need to be in a function that is called from several porcelains\n> > that way.\n>\n> I think moving it to merge_finalize / merge_switch_to_result is indeed a\n> good suggestion, then we might be improving the UX across the board and not just\n> for 'git merge'.\n>\n> >\n> >> diff --git a/merge-ort.c b/merge-ort.c\n> >> index 0d3f42592f..c86ee11614 100644\n> >> --- a/merge-ort.c\n> >> +++ b/merge-ort.c\n> >> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt,\n> >>                         const char *reason = _(\"content\");\n> >>                         if (ci->filemask == 6)\n> >>                                 reason = _(\"add/add\");\n> >> -                       if (S_ISGITLINK(merged_file.mode))\n> >> +                       if (S_ISGITLINK(merged_file.mode)) {\n> >>                                 reason = _(\"submodule\");\n> >> +                               if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> >> +                                       oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid);\n> >> +                                       string_list_append(opt->conflicted_submodule_paths, path);\n> >> +                               }\n> >> +                       }\n> >>                         path_msg(opt, path, 0,\n> >>                                  _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >>                                  reason, path);\n> >> diff --git a/merge-recursive.c b/merge-recursive.c\n> >> index fd1bbde061..ff7cdbefe9 100644\n> >> --- a/merge-recursive.c\n> >> +++ b/merge-recursive.c\n> >> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n> >>         }\n> >>\n> >>         if (!mfi->clean) {\n> >> -               if (S_ISGITLINK(mfi->blob.mode))\n> >> +               if (S_ISGITLINK(mfi->blob.mode)) {\n> >>                         reason = _(\"submodule\");\n> >> +                       if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) {\n> >> +                               oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid);\n> >> +                               string_list_append(opt->conflicted_submodule_paths, path);\n> >> +                       }\n> >> +               }\n> >>                 output(opt, 1, _(\"CONFLICT (%s): Merge conflict in %s\"),\n> >>                                 reason, path);\n> >>                 if (ci && !df_conflict_remains)\n> >\n> > Nice that the changes needed to both the ort and recursive strategies\n> > are so localized.  :-)\n> >\n> >> diff --git a/merge-recursive.h b/merge-recursive.h\n> >> index b88000e3c2..5d267e7a43 100644\n> >> --- a/merge-recursive.h\n> >> +++ b/merge-recursive.h\n> >> @@ -51,6 +51,10 @@ struct merge_options {\n> >>\n> >>         /* internal fields used by the implementation */\n> >>         struct merge_options_internal *priv;\n> >> +\n> >> +       /* fields that hold submodule conflict information */\n> >> +       struct oid_array *conflicted_submodule_oids;\n> >> +       struct string_list *conflicted_submodule_paths;\n> >>  };\n> >\n> > Make sense.\n> >\n> >>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> >> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> >> index 178413c22f..5b384dedc1 100755\n> >> --- a/t/t6437-submodule-merge.sh\n> >> +++ b/t/t6437-submodule-merge.sh\n> >> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n> >>                 test_must_fail git merge c 2> actual\n> >>           fi &&\n> >>          grep $(cat expect) actual > /dev/null &&\n> >> +        test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >>          git reset --hard)\n> >>  '\n> >>\n>\n> In this test, an existing merge does exist, and is suggested to the user with [message 2] above.\n> So telling users to do a new merge in the submodule is maybe to what we want.\n>\n> >> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n> >>          fi &&\n> >>         grep $(cat expect1) actual > /dev/null &&\n> >>         grep $(cat expect2) actual > /dev/null &&\n> >> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)\" actual &&\n> >>         git reset --hard)\n> >>  '\n>\n> Here, 2 existing merges exist, and they are presented to the users with [message 3] above.\n> So again we might not want to tell users to do a new merge.\n>\n> >>\n> >> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' '\n> >>         git commit -a -m \"f\" &&\n> >>\n> >>         git checkout -b test-backward e &&\n> >> -       test_must_fail git merge f)\n> >> +       test_must_fail git merge f >actual &&\n> >> +       test_i18ngrep \"go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)\" actual)\n> >\n> > test_i18ngrep is apparently on the way out:\n> >\n> >     $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh\n> >     # Wrapper for grep which used to be used for\n> >     # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other\n> >     # in-flight changes. Should not be used and will be removed soon.\n> >     test_i18ngrep () {\n> >\n> > I think you just want to use grep instead here for each of these hunks.\n> >\n>\n> Here, one side regresses the submodule commit and the other side advances it.\n> In this case, I really think the right resolution is to choose one side or\n> the other, and not suggest to do a merge at all. So that means that we might\n> want to tweak the advice we give based on the type of submodule conflict...\n>\n> Also, maybe we would want a new test that reproduces exactly the conditions of\n> [message 1], i.e. no existing merge exists in the submodule.\n>\n> Thanks a lot for wanting to improve the submodule UX!\n>\n> Cheers,\n>\n> Philippe.\n"},{"id":"458136","messageId":"20220629224059.1016645-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220610231152.2594428-1-calvinwan@google.com","subject":"[PATCH v3] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-29T22:40:59Z","receivedAt":"2022-06-29T22:41:21Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded, the merge fails and git prints\nthe following error:\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule):Merge conflict in <submodule>\nAutomatic merge failed; fix conflicts and then commit the result.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers. \n\nUpdate error message to match the steps above. If git does not detect a \nmerge resolution, the following is printed:\n\n--------\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; recursive merging with submodules is currently\nnot supported. To manually complete the merge:\n - go to submodule (<submodule>), and merge commit <commit>\n - come back to superproject, and `git add <submodule>` to record the above merge \n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n--------\n\nIf git detects a possible merge resolution, the following is printed:\n\n--------\n\nFailed to merge submodule sub, but a possible merge resolution exists:\n    <commit> Merge branch '<branch1>' into <branch2>\n\n\nIf this is correct simply add it to the index for example\nby using:\n\n  git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n\nwhich will accept this suggestion.\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules is currently not supported.\nTo manually complete the merge:\n - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n - come back to superproject, and `git add <submodule>` to record the above merge \n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n--------\n\nIf git detects multiple possible merge resolutions, the following is printed:\n\n--------\n\nFailed to merge submodule sub, but multiple possible merges exist:\n    <commit> Merge branch '<branch1>' into <branch2>\n    <commit> Merge branch '<branch1>' into <branch3>\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules is currently not supported.\nTo manually complete the merge:\n - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n - come back to superproject, and `git add <submodule>` to record the above merge \n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n-------\n\nChanges since v2:\nThere are three major changes in this patch thanks to all the helpful\nfeedback! I have moved the print function from builtin/merge.c to\nmerge-ort.c and added it to merge_finalize() in merge-recursive.c and\nmerge_switch_to_result() in merge-ort.c. This allows other merge\nmachinery commands besides merge to print submodule conflict advice.\n\nI have moved the check for submodule conflicts from process_entry() to \nmerge_submodule(). This also required removing the early returns and\ninstead going to my submodule conflict check allowing us to pass back\ninformation on whether git detected a possible merge resolution or not.\n\nI have also updated the error message to better reflect the merge\nstatus. Specifically, if git detects a possible merge resolution, the\nerror message now also suggest updating to one of those resolutions.\n\nOther small changes: \n - Moved fields that hold submodule conflict information to new object\n - Shortened printed commit id to DEFAULT_ABBREV\n - Added a test to t6437-submodule-merge.sh for merges with no\n   resolution existence\n - Modified a test in t7402-submodule-rebase to show error message\n   prints in other parts of the merge machinery\n\nChanges since v1:\n - Removed advice to abort merge\n - Error message updated to contain more commit/submodule information\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\n merge-ort.c                 | 53 ++++++++++++++++++++++++++++++++++---\n merge-recursive.c           | 26 +++++++++++++++---\n merge-recursive.h           | 18 +++++++++++++\n t/t6437-submodule-merge.sh  | 36 ++++++++++++++++++++++---\n t/t7402-submodule-rebase.sh |  6 +++--\n 5 files changed, 125 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0d3f42592f..2d9f03ea8c 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -1610,6 +1610,9 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n+\tstruct conflicted_submodule_list *csub = opt->conflicted_submodules;\n+\tstruct conflicted_submodule_item csub_item;\n+\tint resolution_exists = 0;\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n@@ -1619,17 +1622,17 @@ static int merge_submodule(struct merge_options *opt,\n \n \t/* we can not handle deletion conflicts */\n \tif (is_null_oid(o))\n-\t\treturn 0;\n+\t\tgoto ret;\n \tif (is_null_oid(a))\n-\t\treturn 0;\n+\t\tgoto ret;\n \tif (is_null_oid(b))\n-\t\treturn 0;\n+\t\tgoto ret;\n \n \tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n \t\tpath_msg(opt, path, 0,\n \t\t\t\t_(\"Failed to merge submodule %s (not checked out)\"),\n \t\t\t\tpath);\n-\t\treturn 0;\n+\t\tgoto ret;\n \t}\n \n \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n@@ -1703,6 +1706,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   \"which will accept this suggestion.\\n\"),\n \t\t\t oid_to_hex(&merges.objects[0].item->oid), path);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t\tbreak;\n \tdefault:\n \t\tfor (i = 0; i < merges.nr; i++)\n@@ -1712,11 +1716,24 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t _(\"Failed to merge submodule %s, but multiple \"\n \t\t\t   \"possible merges exist:\\n%s\"), path, sb.buf);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t}\n \n \tobject_array_clear(&merges);\n cleanup:\n \trepo_clear(&subrepo);\n+ret:\n+\tif (!ret) {\n+\t\tif (!csub) {\n+\t\t\tCALLOC_ARRAY(csub, 1);\n+\t\t}\n+\t\tcsub_item.oid = *result;\n+\t\tcsub_item.path = xstrdup(path);\n+\t\tcsub_item.resolution_exists = resolution_exists;\n+\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n+\t\tcsub->items[csub->nr++] = csub_item;\n+\t\topt->conflicted_submodules = csub;\n+\t}\n \treturn ret;\n }\n \n@@ -4256,6 +4273,32 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n+\tif (csub && csub->nr > 0) {\n+\t\tint i;\n+\t\tprintf(_(\"Recursive merging with submodules is currently not supported.\\n\"\n+\t\t\t\"To manually complete the merge:\\n\"));\n+\t\tfor (i = 0; i < csub->nr; i++) {\n+\t\t\tconst struct object_id id = csub->items[i].oid;\n+\t\t\tif (csub->items[i].resolution_exists)\n+\t\t\t\tprintf(_(\" - go to submodule (%s), and either update the submodule \"\n+\t\t\t\t\t\"to a possible commit above or merge commit %s\\n\"),\n+\t\t\t\t\tcsub->items[i].path,\n+\t\t\t\t\tfind_unique_abbrev(&id, DEFAULT_ABBREV));\n+\t\t\telse\n+\t\t\t\tprintf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n+\t\t\t\t\tcsub->items[i].path,\n+\t\t\t\t\tfind_unique_abbrev(&id, DEFAULT_ABBREV));\n+\t\t}\n+\t\tprintf(_(\" - come back to superproject, and `git add\"));\n+\t\tfor (i = 0; i < csub->nr; i++)\n+\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n+\t\tprintf(_(\"` to record the above merge \\n\"\n+\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n+\t\t\t\" - commit the resulting index in the superproject\\n\"));\n+\t}\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t}\n \t\tstring_list_clear(&olist, 0);\n \n+\t\tprint_submodule_conflict_suggestion(opt->conflicted_submodules);\n+\n \t\t/* Also include needed rename limit adjustment now */\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opti->renames.needed_limit, 0);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fd1bbde061..311cc37886 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1190,6 +1190,9 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct commit *commit_base, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n+\tstruct conflicted_submodule_list *csub = opt->conflicted_submodules;\n+\tstruct conflicted_submodule_item csub_item;\n+\tint resolution_exists = 0;\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n@@ -1204,15 +1207,15 @@ static int merge_submodule(struct merge_options *opt,\n \n \t/* we can not handle deletion conflicts */\n \tif (is_null_oid(base))\n-\t\treturn 0;\n+\t\tgoto ret;\n \tif (is_null_oid(a))\n-\t\treturn 0;\n+\t\tgoto ret;\n \tif (is_null_oid(b))\n-\t\treturn 0;\n+\t\tgoto ret;\n \n \tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (not checked out)\"), path);\n-\t\treturn 0;\n+\t\tgoto ret;\n \t}\n \n \tif (!(commit_base = lookup_commit_reference(&subrepo, base)) ||\n@@ -1287,17 +1290,31 @@ static int merge_submodule(struct merge_options *opt,\n \t\t       \"  git update-index --cacheinfo 160000 %s \\\"%s\\\"\\n\\n\"\n \t\t       \"which will accept this suggestion.\\n\"),\n \t\t       oid_to_hex(&merges.objects[0].item->oid), path);\n+\t\tresolution_exists = 1;\n \t\tbreak;\n \n \tdefault:\n \t\toutput(opt, 1, _(\"Failed to merge submodule %s (multiple merges found)\"), path);\n \t\tfor (i = 0; i < merges.nr; i++)\n \t\t\tprint_commit(&subrepo, (struct commit *) merges.objects[i].item);\n+\t\tresolution_exists = 1;\n \t}\n \n \tobject_array_clear(&merges);\n cleanup:\n \trepo_clear(&subrepo);\n+ret:\n+\tif (!ret) {\n+\t\tif (!csub) {\n+\t\t\tCALLOC_ARRAY(csub, 1);\n+\t\t}\n+\t\tcsub_item.oid = *result;\n+\t\tcsub_item.path = xstrdup(path);\n+\t\tcsub_item.resolution_exists = resolution_exists;\n+\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n+\t\tcsub->items[csub->nr++] = csub_item;\n+\t\topt->conflicted_submodules = csub;\n+\t}\n \treturn ret;\n }\n \n@@ -3736,6 +3753,7 @@ static void merge_finalize(struct merge_options *opt)\n \tflush_output(opt);\n \tif (!opt->priv->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n+\tprint_submodule_conflict_suggestion(opt->conflicted_submodules);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->priv->needed_rename_limit, 0);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b88000e3c2..b615955fb7 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -9,6 +9,7 @@ struct object_id;\n struct repository;\n struct tree;\n \n+struct conflicted_submodule_list;\n struct merge_options_internal;\n struct merge_options {\n \tstruct repository *repo;\n@@ -51,6 +52,21 @@ struct merge_options {\n \n \t/* internal fields used by the implementation */\n \tstruct merge_options_internal *priv;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct conflicted_submodule_list *conflicted_submodules;\n+};\n+\n+struct conflicted_submodule_item {\n+\tstruct object_id oid;\n+\tchar *path;\n+\tint resolution_exists;\n+};\n+\n+struct conflicted_submodule_list {\n+\tstruct conflicted_submodule_item *items;\n+\tsize_t nr;\n+\tsize_t alloc;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n@@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n+void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub);\n+\n #endif\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 178413c22f..f0a31c3a61 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,28 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n+\n+test_expect_success 'merging should conflict for non fast-forward' '\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t (cd sub &&\n+\t  git rev-parse --short sub-b > ../expect) &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi &&\n+\t grep $(cat expect) actual > /dev/null &&\n+\t sub_expect=\"go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-b)\" &&\n+\t grep \"$sub_expect\" actual &&\n+\t git reset --hard)\n+'\n \n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,9 +149,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n@@ -141,6 +161,9 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n \t grep $(cat expect) actual > /dev/null &&\n+\t sub_expect=\"go to submodule (sub), and either update the submodule to a \\\n+possible commit above or merge commit $(git -C sub rev-parse --short sub-b)\" &&\n+\t grep \"$sub_expect\" actual &&\n \t git reset --hard)\n '\n \n@@ -167,6 +190,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t fi &&\n \tgrep $(cat expect1) actual > /dev/null &&\n \tgrep $(cat expect2) actual > /dev/null &&\n+\tsub_expect=\"go to submodule (sub), and either update the submodule to a \\\n+possible commit above or merge commit $(git -C sub rev-parse --short sub-b)\" &&\n+\tgrep \"$sub_expect\" actual &&\n \tgit reset --hard)\n '\n \n@@ -205,7 +231,9 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tsub_expect=\"go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-a)\" &&\n+\tgrep \"$sub_expect\" actual)\n '\n \n \ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..21c8b4e97c 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tsub_expect=\"go to submodule (submodule), and merge commit $(git -C submodule rev-parse --short HEAD^^)\" &&\n+\tgrep \"$sub_expect\" actual_output\n '\n \n test_done\n\nbase-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n-- \n2.37.0.rc0.161.g10f37bed90-goog\n\n"},{"id":"458152","messageId":"CABPp-BEXQNWCPXr31Oo59577kzoSbfDV=7_7dN8n6KMTsVgsjg@mail.gmail.com","threadId":"57963","inReplyTo":"20220629224059.1016645-1-calvinwan@google.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-30T02:40:09Z","receivedAt":"2022-06-30T02:40:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, Jun 29, 2022 at 3:41 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> When attempting to merge in a superproject with conflicting submodule\n> pointers that cannot be fast-forwarded, the merge fails and git prints\n> the following error:\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule):Merge conflict in <submodule>\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. merge submodules or update submodules to an already existing\n>         commit that reflects the merge\n>  2. add submodules changes to the superproject\n>  3. finish merging superproject\n> These steps are non-obvious for newer submodule users to figure out\n> based on the error message and neither `git submodule status` nor `git\n> status` provide any useful pointers.\n>\n> Update error message to match the steps above. If git does not detect a\n> merge resolution, the following is printed:\n>\n> --------\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Automatic merge failed; recursive merging with submodules is currently\n> not supported. To manually complete the merge:\n>  - go to submodule (<submodule>), and merge commit <commit>\n\nI'm still a little unsure on this.  The merge_submodule() logic we\nhave may not have detected a merge commit that merges the two branches\ntogether, but that doesn't automatically imply a new merge must be\ncreated in the submodule to resolve this conflict.  There might be\nvarious reasons that other existing commits in the submodule could be\nused as the \"correct\" update.\n\nPerhaps that is uncommon enough to not be worth mentioning; I'm just a\nlittle hesitant to treat the merge_submodule() logic as robust and\nfinding the only choices users might want to use.  If we do keep the\nwording as-is, it might be nice to at least discuss in the commit\nmessage why we chose that and ignored the or-update-submodule option\nin this case.\n\n>  - come back to superproject, and `git add <submodule>` to record the above merge\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> --------\n>\n> If git detects a possible merge resolution, the following is printed:\n>\n> --------\n>\n> Failed to merge submodule sub, but a possible merge resolution exists:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>\n>\n> If this is correct simply add it to the index for example\n> by using:\n>\n>   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n>\n> which will accept this suggestion.\n\nThe last few lines above will no longer be part of the output once\nen/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove\ncommand-line-centric submodule message from merge-ort, 2022-06-18).\n\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules is currently not supported.\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n\nAgain, I'm hesitant to treat the suggestions from merge_submodule() as\nthe only possibilities.  For example, perhaps the user will want to\npick a commit that contains one of the ones found by merge_submodule()\nin its history -- perhaps because the newer commit they want to select\ncontains an important bugfix for an issue not discovered at the time\nof the merge in the submodule.\n\nAlso, I'm worried your sentence may be easy to misunderstand, due to\nit being ambiguous whether \"merge\" is a verb or an adjective.  More\nprecisely, your sentence could be read as \"update the submodule to a\npossible commit above, or update the submodule to merge commit\n<commit>\" and then users say, \"But <commit> isn't a merge commit; why\ndoes this message claim it is?  Do I just update the submodule to that\ncommit anyway?\"  Or perhaps users just update it to <commit> without\neven checking to find out that it's not a merge commit, with the\ndeleterious consequences of missing all the important changes\ncurrently found in the submodule.\n\n>  - come back to superproject, and `git add <submodule>` to record the above merge\n\n\"...to record the above merge or update\"?\n\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> --------\n>\n> If git detects multiple possible merge resolutions, the following is printed:\n>\n> --------\n>\n> Failed to merge submodule sub, but multiple possible merges exist:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>     <commit> Merge branch '<branch1>' into <branch3>\n>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules is currently not supported.\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n\nSame issues as I mentioned above for the single-merge-resolution-found case.\n\n>  - come back to superproject, and `git add <submodule>` to record the above merge\n\n\"merge or update\", right?\n\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> -------\n>\n> Changes since v2:\n> There are three major changes in this patch thanks to all the helpful\n> feedback! I have moved the print function from builtin/merge.c to\n> merge-ort.c and added it to merge_finalize() in merge-recursive.c and\n> merge_switch_to_result() in merge-ort.c. This allows other merge\n> machinery commands besides merge to print submodule conflict advice.\n>\n> I have moved the check for submodule conflicts from process_entry() to\n> merge_submodule(). This also required removing the early returns and\n> instead going to my submodule conflict check allowing us to pass back\n> information on whether git detected a possible merge resolution or not.\n>\n> I have also updated the error message to better reflect the merge\n> status. Specifically, if git detects a possible merge resolution, the\n> error message now also suggest updating to one of those resolutions.\n>\n> Other small changes:\n>  - Moved fields that hold submodule conflict information to new object\n>  - Shortened printed commit id to DEFAULT_ABBREV\n>  - Added a test to t6437-submodule-merge.sh for merges with no\n>    resolution existence\n>  - Modified a test in t7402-submodule-rebase to show error message\n>    prints in other parts of the merge machinery\n>\n> Changes since v1:\n>  - Removed advice to abort merge\n>  - Error message updated to contain more commit/submodule information\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> ---\n>  merge-ort.c                 | 53 ++++++++++++++++++++++++++++++++++---\n>  merge-recursive.c           | 26 +++++++++++++++---\n>  merge-recursive.h           | 18 +++++++++++++\n>  t/t6437-submodule-merge.sh  | 36 ++++++++++++++++++++++---\n>  t/t7402-submodule-rebase.sh |  6 +++--\n>  5 files changed, 125 insertions(+), 14 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 0d3f42592f..2d9f03ea8c 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -1610,6 +1610,9 @@ static int merge_submodule(struct merge_options *opt,\n>         struct commit *commit_o, *commit_a, *commit_b;\n>         int parent_count;\n>         struct object_array merges;\n> +       struct conflicted_submodule_list *csub = opt->conflicted_submodules;\n> +       struct conflicted_submodule_item csub_item;\n> +       int resolution_exists = 0;\n>\n>         int i;\n>         int search = !opt->priv->call_depth;\n> @@ -1619,17 +1622,17 @@ static int merge_submodule(struct merge_options *opt,\n>\n>         /* we can not handle deletion conflicts */\n>         if (is_null_oid(o))\n> -               return 0;\n> +               goto ret;\n>         if (is_null_oid(a))\n> -               return 0;\n> +               goto ret;\n>         if (is_null_oid(b))\n> -               return 0;\n> +               goto ret;\n>\n>         if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n>                 path_msg(opt, path, 0,\n>                                 _(\"Failed to merge submodule %s (not checked out)\"),\n>                                 path);\n> -               return 0;\n> +               goto ret;\n>         }\n>\n>         if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n> @@ -1703,6 +1706,7 @@ static int merge_submodule(struct merge_options *opt,\n>                            \"which will accept this suggestion.\\n\"),\n>                          oid_to_hex(&merges.objects[0].item->oid), path);\n>                 strbuf_release(&sb);\n> +               resolution_exists = 1;\n>                 break;\n>         default:\n>                 for (i = 0; i < merges.nr; i++)\n> @@ -1712,11 +1716,24 @@ static int merge_submodule(struct merge_options *opt,\n>                          _(\"Failed to merge submodule %s, but multiple \"\n>                            \"possible merges exist:\\n%s\"), path, sb.buf);\n>                 strbuf_release(&sb);\n> +               resolution_exists = 1;\n>         }\n>\n>         object_array_clear(&merges);\n>  cleanup:\n>         repo_clear(&subrepo);\n> +ret:\n> +       if (!ret) {\n> +               if (!csub) {\n> +                       CALLOC_ARRAY(csub, 1);\n> +               }\n> +               csub_item.oid = *result;\n\nShouldn't this be \"*b\" rather than \"*result\"?\n\nAlso, are we risking telling the user to \"merge commit\n0000000000000000000000000000000000000000\" from the submodule, given\nsome of the early exits that you redirected to come through here?\n\n> +               csub_item.path = xstrdup(path);\n> +               csub_item.resolution_exists = resolution_exists;\n> +               ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n> +               csub->items[csub->nr++] = csub_item;\n> +               opt->conflicted_submodules = csub;\n> +       }\n>         return ret;\n>  }\n>\n> @@ -4256,6 +4273,32 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>         return errs;\n>  }\n>\n> +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n\nMaybe just make this function be static?\n\n> +       if (csub && csub->nr > 0) {\n> +               int i;\n> +               printf(_(\"Recursive merging with submodules is currently not supported.\\n\"\n> +                       \"To manually complete the merge:\\n\"));\n> +               for (i = 0; i < csub->nr; i++) {\n> +                       const struct object_id id = csub->items[i].oid;\n> +                       if (csub->items[i].resolution_exists)\n> +                               printf(_(\" - go to submodule (%s), and either update the submodule \"\n> +                                       \"to a possible commit above or merge commit %s\\n\"),\n> +                                       csub->items[i].path,\n> +                                       find_unique_abbrev(&id, DEFAULT_ABBREV));\n\nShouldn't this be repo_find_unique_abbrev(), and in particular with\nthe submodule repository being passed to it?  Or perhaps using the\nformat_commit() function, since merge_submodule() is already using it\nfor the earlier submodule-related messages?\n\n> +                       else\n> +                               printf(_(\" - go to submodule (%s), and merge commit %s\\n\"),\n> +                                       csub->items[i].path,\n> +                                       find_unique_abbrev(&id, DEFAULT_ABBREV));\n\nLikewise?\n\n> +               }\n> +               printf(_(\" - come back to superproject, and `git add\"));\n> +               for (i = 0; i < csub->nr; i++)\n> +                       printf(_(\" %s\"), csub->items[i].path);\n> +               printf(_(\"` to record the above merge \\n\"\n> +                       \" - resolve any other conflicts in the superproject\\n\"\n> +                       \" - commit the resulting index in the superproject\\n\"));\n> +       }\n> +}\n> +\n>  void merge_switch_to_result(struct merge_options *opt,\n>                             struct tree *head,\n>                             struct merge_result *result,\n> @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt,\n>                 }\n>                 string_list_clear(&olist, 0);\n>\n> +               print_submodule_conflict_suggestion(opt->conflicted_submodules);\n> +\n>                 /* Also include needed rename limit adjustment now */\n>                 diff_warn_rename_limit(\"merge.renamelimit\",\n>                                        opti->renames.needed_limit, 0);\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index fd1bbde061..311cc37886 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n\nIs it worth updating merge-recursive.c here?  I'd rather only give it\nsecurity fix updates until we delete it.\n\n> @@ -1190,6 +1190,9 @@ static int merge_submodule(struct merge_options *opt,\n>         struct commit *commit_base, *commit_a, *commit_b;\n>         int parent_count;\n>         struct object_array merges;\n> +       struct conflicted_submodule_list *csub = opt->conflicted_submodules;\n> +       struct conflicted_submodule_item csub_item;\n> +       int resolution_exists = 0;\n>\n>         int i;\n>         int search = !opt->priv->call_depth;\n> @@ -1204,15 +1207,15 @@ static int merge_submodule(struct merge_options *opt,\n>\n>         /* we can not handle deletion conflicts */\n>         if (is_null_oid(base))\n> -               return 0;\n> +               goto ret;\n>         if (is_null_oid(a))\n> -               return 0;\n> +               goto ret;\n>         if (is_null_oid(b))\n> -               return 0;\n> +               goto ret;\n>\n>         if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n>                 output(opt, 1, _(\"Failed to merge submodule %s (not checked out)\"), path);\n> -               return 0;\n> +               goto ret;\n>         }\n>\n>         if (!(commit_base = lookup_commit_reference(&subrepo, base)) ||\n> @@ -1287,17 +1290,31 @@ static int merge_submodule(struct merge_options *opt,\n>                        \"  git update-index --cacheinfo 160000 %s \\\"%s\\\"\\n\\n\"\n>                        \"which will accept this suggestion.\\n\"),\n>                        oid_to_hex(&merges.objects[0].item->oid), path);\n> +               resolution_exists = 1;\n>                 break;\n>\n>         default:\n>                 output(opt, 1, _(\"Failed to merge submodule %s (multiple merges found)\"), path);\n>                 for (i = 0; i < merges.nr; i++)\n>                         print_commit(&subrepo, (struct commit *) merges.objects[i].item);\n> +               resolution_exists = 1;\n>         }\n>\n>         object_array_clear(&merges);\n>  cleanup:\n>         repo_clear(&subrepo);\n> +ret:\n> +       if (!ret) {\n> +               if (!csub) {\n> +                       CALLOC_ARRAY(csub, 1);\n> +               }\n> +               csub_item.oid = *result;\n> +               csub_item.path = xstrdup(path);\n> +               csub_item.resolution_exists = resolution_exists;\n> +               ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n> +               csub->items[csub->nr++] = csub_item;\n> +               opt->conflicted_submodules = csub;\n> +       }\n>         return ret;\n>  }\n>\n> @@ -3736,6 +3753,7 @@ static void merge_finalize(struct merge_options *opt)\n>         flush_output(opt);\n>         if (!opt->priv->call_depth && opt->buffer_output < 2)\n>                 strbuf_release(&opt->obuf);\n> +       print_submodule_conflict_suggestion(opt->conflicted_submodules);\n>         if (show(opt, 2))\n>                 diff_warn_rename_limit(\"merge.renamelimit\",\n>                                        opt->priv->needed_rename_limit, 0);\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index b88000e3c2..b615955fb7 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -9,6 +9,7 @@ struct object_id;\n>  struct repository;\n>  struct tree;\n>\n> +struct conflicted_submodule_list;\n>  struct merge_options_internal;\n>  struct merge_options {\n>         struct repository *repo;\n> @@ -51,6 +52,21 @@ struct merge_options {\n>\n>         /* internal fields used by the implementation */\n>         struct merge_options_internal *priv;\n> +\n> +       /* field that holds submodule conflict information */\n> +       struct conflicted_submodule_list *conflicted_submodules;\n\nI think this should be added to merge_options_internal rather than to\nmerge_options.  There's no need for an API caller to make use of\nthese.\n\nAlso, if a clear need arises later for API callers to make use of this\ninformation, then it should be part of merge_result for merge-ort.c,\nnot part of merge_options.\n\n> +};\n> +\n> +struct conflicted_submodule_item {\n> +       struct object_id oid;\n> +       char *path;\n> +       int resolution_exists;\n> +};\n> +\n> +struct conflicted_submodule_list {\n> +       struct conflicted_submodule_item *items;\n> +       size_t nr;\n> +       size_t alloc;\n>  };\n\nSimilarly, I think these should be defined in merge-ort.c (and maybe\nalso merge-recursive.c) near struct merge_options_internal.\n\n>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt,\n>                             const struct object_id **merge_bases,\n>                             struct commit **result);\n>\n> +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub);\n\nand I think there's no reason to put this in the header file; it\nshould just be a static function in merge-ort.c.  (And, if we really\nwant to update merge-recursive.c, then copy the function over there.\n*Nothing* in merge-ort.c should call a function in merge-recursive.c\nand similarly nothing in merge-recursive.c should call any function in\nmerge-ort.c.  Yes, that implies some duplication.  There is a fair\namount already and we've discussed it, and chosen against creating a\nshared module as well.  I want merge-recursive to not be broken by\nmerge-ort updates; it should remain stable until the day we finally\nget to nuke it.  I'm personally of the opinion that merge-recursive\nshould only get security fixes at this point, though I haven't pinged\nto see if other folks agree with that point of view yet or not.  I'm\nnot wasting my time fixing/improving it, though...)\n\n> +\n>  #endif\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index 178413c22f..f0a31c3a61 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -103,8 +103,28 @@ test_expect_success 'setup for merge search' '\n>          echo \"file-c\" > file-c &&\n>          git add file-c &&\n>          git commit -m \"sub-c\") &&\n> -       git commit -a -m \"c\" &&\n> +       git commit -a -m \"c\")\n> +'\n> +\n> +test_expect_success 'merging should conflict for non fast-forward' '\n> +       (cd merge-search &&\n> +        git checkout -b test-nonforward-a b &&\n> +        (cd sub &&\n> +         git rev-parse --short sub-b > ../expect) &&\n> +         if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +         then\n> +               test_must_fail git merge c >actual\n> +         else\n> +               test_must_fail git merge c 2> actual\n> +         fi &&\n> +        grep $(cat expect) actual > /dev/null &&\n> +        sub_expect=\"go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-b)\" &&\n> +        grep \"$sub_expect\" actual &&\n> +        git reset --hard)\n> +'\n>\n> +test_expect_success 'finish setup for merge-search' '\n> +       (cd merge-search &&\n>         git checkout -b d a &&\n>         (cd sub &&\n>          git checkout -b sub-d sub-b &&\n> @@ -129,9 +149,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n>          test_cmp expect actual)\n>  '\n>\n> -test_expect_success 'merging should conflict for non fast-forward' '\n> +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n>         (cd merge-search &&\n> -        git checkout -b test-nonforward b &&\n> +        git checkout -b test-nonforward-b b &&\n>          (cd sub &&\n>           git rev-parse sub-d > ../expect) &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> @@ -141,6 +161,9 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n>          grep $(cat expect) actual > /dev/null &&\n> +        sub_expect=\"go to submodule (sub), and either update the submodule to a \\\n> +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)\" &&\n> +        grep \"$sub_expect\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -167,6 +190,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          fi &&\n>         grep $(cat expect1) actual > /dev/null &&\n>         grep $(cat expect2) actual > /dev/null &&\n> +       sub_expect=\"go to submodule (sub), and either update the submodule to a \\\n> +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)\" &&\n> +       grep \"$sub_expect\" actual &&\n>         git reset --hard)\n>  '\n>\n> @@ -205,7 +231,9 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>         git commit -a -m \"f\" &&\n>\n>         git checkout -b test-backward e &&\n> -       test_must_fail git merge f)\n> +       test_must_fail git merge f >actual &&\n> +       sub_expect=\"go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-a)\" &&\n> +       grep \"$sub_expect\" actual)\n>  '\n>\n>\n> diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\n> index 8e32f19007..21c8b4e97c 100755\n> --- a/t/t7402-submodule-rebase.sh\n> +++ b/t/t7402-submodule-rebase.sh\n> @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n>         test_tick &&\n>         git commit -m fourth &&\n>\n> -       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n> +       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n>         git ls-files -s submodule >actual &&\n>         (\n>                 cd submodule &&\n> @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' '\n>                 echo \"160000 $(git rev-parse HEAD^^) 2  submodule\" &&\n>                 echo \"160000 $(git rev-parse HEAD) 3    submodule\"\n>         ) >expect &&\n> -       test_cmp expect actual\n> +       test_cmp expect actual &&\n> +       sub_expect=\"go to submodule (submodule), and merge commit $(git -C submodule rev-parse --short HEAD^^)\" &&\n> +       grep \"$sub_expect\" actual_output\n>  '\n>\n>  test_done\n>\n> base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n> --\n> 2.37.0.rc0.161.g10f37bed90-goog\n\nDespite my many nitpicks and whatnot, it looks like your change will\nmake things nicer for the user, and I think your patch is coming along\nnicely.  There are some things to fix up, but the overall direction\nseems good.\n"},{"id":"458243","messageId":"20220630194833.1191949-1-calvinwan@google.com","threadId":"57963","inReplyTo":"CABPp-BEXQNWCPXr31Oo59577kzoSbfDV=7_7dN8n6KMTsVgsjg@mail.gmail.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-30T19:48:33Z","receivedAt":"2022-06-30T19:49:18Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> > Failed to merge submodule <submodule>\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Automatic merge failed; recursive merging with submodules is currently\n> > not supported. To manually complete the merge:\n> >  - go to submodule (<submodule>), and merge commit <commit>\n> \n> I'm still a little unsure on this.  The merge_submodule() logic we\n> have may not have detected a merge commit that merges the two branches\n> together, but that doesn't automatically imply a new merge must be\n> created in the submodule to resolve this conflict.  There might be\n> various reasons that other existing commits in the submodule could be\n> used as the \"correct\" update.\n> \n> Perhaps that is uncommon enough to not be worth mentioning; I'm just a\n> little hesitant to treat the merge_submodule() logic as robust and\n> finding the only choices users might want to use.  If we do keep the\n> wording as-is, it might be nice to at least discuss in the commit\n> message why we chose that and ignored the or-update-submodule option\n> in this case.\n\nYou make a good point about merge_submodule() possibly not finding all\nof the right choices -- I'll add back the or-update-submodule option\nback.\n\n> > If this is correct simply add it to the index for example\n> > by using:\n> >\n> >   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n> >\n> > which will accept this suggestion.\n> \n> The last few lines above will no longer be part of the output once\n> en/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove\n> command-line-centric submodule message from merge-ort, 2022-06-18).\n\nack\n\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Recursive merging with submodules is currently not supported.\n> > To manually complete the merge:\n> >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> \n> Again, I'm hesitant to treat the suggestions from merge_submodule() as\n> the only possibilities.  For example, perhaps the user will want to\n> pick a commit that contains one of the ones found by merge_submodule()\n> in its history -- perhaps because the newer commit they want to select\n> contains an important bugfix for an issue not discovered at the time\n> of the merge in the submodule.\n> \n> Also, I'm worried your sentence may be easy to misunderstand, due to\n> it being ambiguous whether \"merge\" is a verb or an adjective.  More\n> precisely, your sentence could be read as \"update the submodule to a\n> possible commit above, or update the submodule to merge commit\n> <commit>\" and then users say, \"But <commit> isn't a merge commit; why\n> does this message claim it is?  Do I just update the submodule to that\n> commit anyway?\"  Or perhaps users just update it to <commit> without\n> even checking to find out that it's not a merge commit, with the\n> deleterious consequences of missing all the important changes\n> currently found in the submodule.\n\nI agree this sentence can be misinterpreted. I also think the main\nreason my current message is unclear is because there is not enough\ncontext for the user to understand why the merge failed. In a standard\nmerge, the reason in \"CONFLICT (<reason>)\" provides context, whereas in\nthis case, \"CONFLICT (submodule)\" only tells the user that the submodule\nis conflicted in some way. The user is unaware that we attempted to\nfast-forward the submodule, failed, and now require the user to either\nupdate the commit or merge the commit. How does this rewording sound?\n\n Automatic merge failed; recursive merging with submodules currently\n only supports fast-forward merges. For each conflicted submodule, if\n the current commit does not reflect the desired state, either update or\n merge the commit. This can be accomplished with the following steps:\n\n - go to submodule (<submodule>), and either update the commit or merge commit <commit>\n\n> >  - come back to superproject, and `git add <submodule>` to record the above merge\n> \n> \"...to record the above merge or update\"?\n\nwill... \"update\" haha\n\n> > If git detects multiple possible merge resolutions, the following is printed:\n> >\n> > --------\n> >\n> > Failed to merge submodule sub, but multiple possible merges exist:\n> >     <commit> Merge branch '<branch1>' into <branch2>\n> >     <commit> Merge branch '<branch1>' into <branch3>\n> >\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Recursive merging with submodules is currently not supported.\n> > To manually complete the merge:\n> >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> \n> Same issues as I mentioned above for the single-merge-resolution-found case.\n> \n\nditto\n\n> >  - come back to superproject, and `git add <submodule>` to record the above merge\n> \n> \"merge or update\", right?\n\nditto\n\n> > +ret:\n> > +       if (!ret) {\n> > +               if (!csub) {\n> > +                       CALLOC_ARRAY(csub, 1);\n> > +               }\n> > +               csub_item.oid = *result;\n> \n> Shouldn't this be \"*b\" rather than \"*result\"?\n\nYes it should\n\n> \n> Also, are we risking telling the user to \"merge commit\n> 0000000000000000000000000000000000000000\" from the submodule, given\n> some of the early exits that you redirected to come through here?\n\nYou are correct, but I'm not quite sure what should happen in this case. What does it mean for either o, a, or b to be null? Did a submodule get deleted on one side? \n\n> > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n> \n> Maybe just make this function be static?\n\nIt should be static now that this won't be called in merge-recursive\n\n> > +                                       find_unique_abbrev(&id, DEFAULT_ABBREV));\n> \n> Shouldn't this be repo_find_unique_abbrev(), and in particular with\n> the submodule repository being passed to it?  Or perhaps using the\n> format_commit() function, since merge_submodule() is already using it\n> for the earlier submodule-related messages?\n\nIt should and I will go with the format_commit() option so I don't have to pass subrepo information into the print function.\n\n> >  void merge_switch_to_result(struct merge_options *opt,\n> >                             struct tree *head,\n> >                             struct merge_result *result,\n> > @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt,\n> >                 }\n> >                 string_list_clear(&olist, 0);\n> >\n> > +               print_submodule_conflict_suggestion(opt->conflicted_submodules);\n> > +\n> >                 /* Also include needed rename limit adjustment now */\n> >                 diff_warn_rename_limit(\"merge.renamelimit\",\n> >                                        opti->renames.needed_limit, 0);\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n> > index fd1bbde061..311cc37886 100644\n> > --- a/merge-recursive.c\n> > +++ b/merge-recursive.c\n> \n> Is it worth updating merge-recursive.c here?  I'd rather only give it\n> security fix updates until we delete it.\n\nAh wasn't aware that was the status of merge-recursive. Will delete.\n\n> > diff --git a/merge-recursive.h b/merge-recursive.h\n> > index b88000e3c2..b615955fb7 100644\n> > --- a/merge-recursive.h\n> > +++ b/merge-recursive.h\n> > @@ -9,6 +9,7 @@ struct object_id;\n> >  struct repository;\n> >  struct tree;\n> >\n> > +struct conflicted_submodule_list;\n> >  struct merge_options_internal;\n> >  struct merge_options {\n> >         struct repository *repo;\n> > @@ -51,6 +52,21 @@ struct merge_options {\n> >\n> >         /* internal fields used by the implementation */\n> >         struct merge_options_internal *priv;\n> > +\n> > +       /* field that holds submodule conflict information */\n> > +       struct conflicted_submodule_list *conflicted_submodules;\n> \n> I think this should be added to merge_options_internal rather than to\n> merge_options.  There's no need for an API caller to make use of\n> these.\n> \n> Also, if a clear need arises later for API callers to make use of this\n> information, then it should be part of merge_result for merge-ort.c,\n> not part of merge_options.\n\nack\n\n> > +};\n> > +\n> > +struct conflicted_submodule_item {\n> > +       struct object_id oid;\n> > +       char *path;\n> > +       int resolution_exists;\n> > +};\n> > +\n> > +struct conflicted_submodule_list {\n> > +       struct conflicted_submodule_item *items;\n> > +       size_t nr;\n> > +       size_t alloc;\n> >  };\n> \n> Similarly, I think these should be defined in merge-ort.c (and maybe\n> also merge-recursive.c) near struct merge_options_internal.\n\nack\n\n> >  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> > @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt,\n> >                             const struct object_id **merge_bases,\n> >                             struct commit **result);\n> >\n> > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub);\n> \n> and I think there's no reason to put this in the header file; it\n> should just be a static function in merge-ort.c.  (And, if we really\n> want to update merge-recursive.c, then copy the function over there.\n> *Nothing* in merge-ort.c should call a function in merge-recursive.c\n> and similarly nothing in merge-recursive.c should call any function in\n> merge-ort.c.  Yes, that implies some duplication.  There is a fair\n> amount already and we've discussed it, and chosen against creating a\n> shared module as well.  I want merge-recursive to not be broken by\n> merge-ort updates; it should remain stable until the day we finally\n> get to nuke it.  I'm personally of the opinion that merge-recursive\n> should only get security fixes at this point, though I haven't pinged\n> to see if other folks agree with that point of view yet or not.  I'm\n> not wasting my time fixing/improving it, though...)\n\nGood to know going forward I should only update merge-ort (unless for security fixes). I'll keep this in mind refactoring my patch.\n \n> Despite my many nitpicks and whatnot, it looks like your change will\n> make things nicer for the user, and I think your patch is coming along\n> nicely.  There are some things to fix up, but the overall direction\n> seems good.\n\nThank you for all of the helpful feedback!\n"},{"id":"458245","messageId":"kl6l35flubx8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"57963","inReplyTo":"20220629224059.1016645-1-calvinwan@google.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-06-30T20:35:47Z","receivedAt":"2022-06-30T20:35:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Hi! I have a suggestion for the output text; I haven't looked closely at\nthe code changes.\n\nCalvin Wan <calvinwan@google.com> writes:\n\n>  Changes since v2:\n>  [...]\n>  Changes since v1:\n>  [...]\n\nI notice that this is all above the \"---\", i.e. this becomes part of the\ncommit message when \"git am\"-ed. Intentional?\n\n> If git detects a possible merge resolution, the following is printed:\n>\n> --------\n>\n> Failed to merge submodule sub, but a possible merge resolution exists:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>\n>\n> If this is correct simply add it to the index for example\n> by using:\n>\n>   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n>\n> which will accept this suggestion.\n>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules is currently not supported.\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n>  - come back to superproject, and `git add <submodule>` to record the above merge \n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> --------\n\nI'm hesitant to recommend a plumbing command like \"git update-index\" to\nthe user, especially if the user is one who needs help resolving a\nsubmodule merge conflict. I also believe this would be the first time we\nrecommend \"git update-index\".\n\nTo do this using only porcelain commands, maybe:\n\n   git -C <submodule> checkout <commit> &&\n   git add <submodule>\n\n(Though this might need to be broken up into two commands because I'm\nnot sure if we ever include \"&&\" in a help message. I'm guessing we\ndon't for portability reasons?)\n\n> If git detects multiple possible merge resolutions, the following is printed:\n>\n> --------\n>\n> Failed to merge submodule sub, but multiple possible merges exist:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>     <commit> Merge branch '<branch1>' into <branch3>\n>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules is currently not supported.\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n>  - come back to superproject, and `git add <submodule>` to record the above merge \n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> -------\n\nFor consistency, perhaps include the \"here's how to use the suggestion\"\ninstructions here as well?\n"},{"id":"458247","messageId":"kl6lzghtsww9.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"57963","inReplyTo":"kl6l35flubx8.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-06-30T20:45:42Z","receivedAt":"2022-06-30T20:45:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> Hi! I have a suggestion for the output text; I haven't looked closely at\n> the code changes.\n>\n>> If git detects a possible merge resolution, the following is printed:\n>>\n>> --------\n>>\n>> Failed to merge submodule sub, but a possible merge resolution exists:\n>>     <commit> Merge branch '<branch1>' into <branch2>\n>>\n>>\n>> If this is correct simply add it to the index for example\n>> by using:\n>>\n>>   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n>>\n>> which will accept this suggestion.\n>>\n>> CONFLICT (submodule): Merge conflict in <submodule>\n>> Recursive merging with submodules is currently not supported.\n>> To manually complete the merge:\n>>  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n>>  - come back to superproject, and `git add <submodule>` to record the above merge \n>>  - resolve any other conflicts in the superproject\n>>  - commit the resulting index in the superproject\n>> Automatic merge failed; fix conflicts and then commit the result.\n>>\n>> --------\n>\n> I'm hesitant to recommend a plumbing command like \"git update-index\" to\n> the user, especially if the user is one who needs help resolving a\n> submodule merge conflict. I also believe this would be the first time we\n> recommend \"git update-index\".\n>\n> To do this using only porcelain commands, maybe:\n>\n>    git -C <submodule> checkout <commit> &&\n>    git add <submodule>\n>\n> (Though this might need to be broken up into two commands because I'm\n> not sure if we ever include \"&&\" in a help message. I'm guessing we\n> don't for portability reasons?)\n\nGah, ignore everything I said here. I should have read the thread\ncloser:\n- The update-index suggestion didn't come from you; it had already\n  existed prior to this series.\n- Both Philppe and Elijah have already suggested the exact same thing.\n\n"},{"id":"458250","messageId":"CAFySSZDOC8a8q+P3hTAMmO4TCi8Aj61432CsAhgh+pKBv=owyg@mail.gmail.com","threadId":"57963","inReplyTo":"kl6l35flubx8.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-06-30T21:08:57Z","receivedAt":"2022-06-30T21:09:12Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> I notice that this is all above the \"---\", i.e. this becomes part of the\n> commit message when \"git am\"-ed. Intentional?\n\nGood catch. Not intentional at all.\n\nOn Thu, Jun 30, 2022 at 1:35 PM Glen Choo <chooglen@google.com> wrote:\n>\n> Hi! I have a suggestion for the output text; I haven't looked closely at\n> the code changes.\n>\n> Calvin Wan <calvinwan@google.com> writes:\n>\n> >  Changes since v2:\n> >  [...]\n> >  Changes since v1:\n> >  [...]\n>\n> I notice that this is all above the \"---\", i.e. this becomes part of the\n> commit message when \"git am\"-ed. Intentional?\n>\n> > If git detects a possible merge resolution, the following is printed:\n> >\n> > --------\n> >\n> > Failed to merge submodule sub, but a possible merge resolution exists:\n> >     <commit> Merge branch '<branch1>' into <branch2>\n> >\n> >\n> > If this is correct simply add it to the index for example\n> > by using:\n> >\n> >   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n> >\n> > which will accept this suggestion.\n> >\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Recursive merging with submodules is currently not supported.\n> > To manually complete the merge:\n> >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> >  - come back to superproject, and `git add <submodule>` to record the above merge\n> >  - resolve any other conflicts in the superproject\n> >  - commit the resulting index in the superproject\n> > Automatic merge failed; fix conflicts and then commit the result.\n> >\n> > --------\n>\n> I'm hesitant to recommend a plumbing command like \"git update-index\" to\n> the user, especially if the user is one who needs help resolving a\n> submodule merge conflict. I also believe this would be the first time we\n> recommend \"git update-index\".\n>\n> To do this using only porcelain commands, maybe:\n>\n>    git -C <submodule> checkout <commit> &&\n>    git add <submodule>\n>\n> (Though this might need to be broken up into two commands because I'm\n> not sure if we ever include \"&&\" in a help message. I'm guessing we\n> don't for portability reasons?)\n>\n> > If git detects multiple possible merge resolutions, the following is printed:\n> >\n> > --------\n> >\n> > Failed to merge submodule sub, but multiple possible merges exist:\n> >     <commit> Merge branch '<branch1>' into <branch2>\n> >     <commit> Merge branch '<branch1>' into <branch3>\n> >\n> > CONFLICT (submodule): Merge conflict in <submodule>\n> > Recursive merging with submodules is currently not supported.\n> > To manually complete the merge:\n> >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> >  - come back to superproject, and `git add <submodule>` to record the above merge\n> >  - resolve any other conflicts in the superproject\n> >  - commit the resulting index in the superproject\n> > Automatic merge failed; fix conflicts and then commit the result.\n> >\n> > -------\n>\n> For consistency, perhaps include the \"here's how to use the suggestion\"\n> instructions here as well?\n"},{"id":"458333","messageId":"CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com","threadId":"57963","inReplyTo":"20220630194833.1191949-1-calvinwan@google.com","subject":"Re: [PATCH v3] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-01T04:27:13Z","receivedAt":"2022-07-01T04:27:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jun 30, 2022 at 12:48 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> > > Failed to merge submodule <submodule>\n> > > CONFLICT (submodule): Merge conflict in <submodule>\n> > > Automatic merge failed; recursive merging with submodules is currently\n> > > not supported. To manually complete the merge:\n> > >  - go to submodule (<submodule>), and merge commit <commit>\n> >\n> > I'm still a little unsure on this.  The merge_submodule() logic we\n> > have may not have detected a merge commit that merges the two branches\n> > together, but that doesn't automatically imply a new merge must be\n> > created in the submodule to resolve this conflict.  There might be\n> > various reasons that other existing commits in the submodule could be\n> > used as the \"correct\" update.\n> >\n> > Perhaps that is uncommon enough to not be worth mentioning; I'm just a\n> > little hesitant to treat the merge_submodule() logic as robust and\n> > finding the only choices users might want to use.  If we do keep the\n> > wording as-is, it might be nice to at least discuss in the commit\n> > message why we chose that and ignored the or-update-submodule option\n> > in this case.\n>\n> You make a good point about merge_submodule() possibly not finding all\n> of the right choices -- I'll add back the or-update-submodule option\n> back.\n>\n> > > If this is correct simply add it to the index for example\n> > > by using:\n> > >\n> > >   git update-index --cacheinfo 160000 <commit> \"<submodule>\"\n> > >\n> > > which will accept this suggestion.\n> >\n> > The last few lines above will no longer be part of the output once\n> > en/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove\n> > command-line-centric submodule message from merge-ort, 2022-06-18).\n>\n> ack\n>\n> > > CONFLICT (submodule): Merge conflict in <submodule>\n> > > Recursive merging with submodules is currently not supported.\n> > > To manually complete the merge:\n> > >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> >\n> > Again, I'm hesitant to treat the suggestions from merge_submodule() as\n> > the only possibilities.  For example, perhaps the user will want to\n> > pick a commit that contains one of the ones found by merge_submodule()\n> > in its history -- perhaps because the newer commit they want to select\n> > contains an important bugfix for an issue not discovered at the time\n> > of the merge in the submodule.\n> >\n> > Also, I'm worried your sentence may be easy to misunderstand, due to\n> > it being ambiguous whether \"merge\" is a verb or an adjective.  More\n> > precisely, your sentence could be read as \"update the submodule to a\n> > possible commit above, or update the submodule to merge commit\n> > <commit>\" and then users say, \"But <commit> isn't a merge commit; why\n> > does this message claim it is?  Do I just update the submodule to that\n> > commit anyway?\"  Or perhaps users just update it to <commit> without\n> > even checking to find out that it's not a merge commit, with the\n> > deleterious consequences of missing all the important changes\n> > currently found in the submodule.\n\nI probably should have mentioned that listing \"merge\" first and\n\"update\" second in your instructions at least avoids the ambiguity\nbecause it makes it clear that \"merge\" is a verb that way:\n\n    - go to submodule (<submodule>), and either merge commit <commit>\nor update the submodule to a possible commit above\n\n> I agree this sentence can be misinterpreted. I also think the main\n> reason my current message is unclear is because there is not enough\n> context for the user to understand why the merge failed. In a standard\n> merge, the reason in \"CONFLICT (<reason>)\" provides context, whereas in\n> this case, \"CONFLICT (submodule)\" only tells the user that the submodule\n> is conflicted in some way. The user is unaware that we attempted to\n> fast-forward the submodule, failed, and now require the user to either\n> update the commit or merge the commit. How does this rewording sound?\n\nDo they need to know what we attempted?  I'm not sure why that\nmatters, beyond maybe telling them that actual automatic merging of\nsubmodules is currently only done by Git's merging machinery in very\ntrivial cases.  All that should really matter at this point is that\nthere was a submodule modified on both sides of history, and we need\nthem to handle the merge of the submodule.\n\n>  Automatic merge failed; recursive merging with submodules currently\n>  only supports fast-forward merges. For each conflicted submodule, if\n\nI'd rather substitute \"trivial cases\" instead of \"fast-forward\nmerges\".  For example, we handle deletions on both sides, it's just\nthat that's done before it ever gets to merge_submodule().  And if we\nadd more types of special cases beyond fast-forwards at some point,\nit'd be nice to not need to update this text.\n\n>  the current commit does not reflect the desired state, either update or\n>  merge the commit. This can be accomplished with the following steps:\n\nMaybe replace the last two sentences with \"Please manually handle the\nmerging of each conflicted submodule; this can be accomplished with\nthe following steps:\"\n\n>  - go to submodule (<submodule>), and either update the commit or merge commit <commit>\n\nWhat would you think of switching this to\n\n   - go to submodule <submodule>, and either merge commit <commit> or\nupdate to an existing commit which has merged those changes such as\none of the ones listed above\n\n> > >  - come back to superproject, and `git add <submodule>` to record the above merge\n> >\n> > \"...to record the above merge or update\"?\n>\n> will... \"update\" haha\n\n:-)\n\n> > > If git detects multiple possible merge resolutions, the following is printed:\n> > >\n> > > --------\n> > >\n> > > Failed to merge submodule sub, but multiple possible merges exist:\n> > >     <commit> Merge branch '<branch1>' into <branch2>\n> > >     <commit> Merge branch '<branch1>' into <branch3>\n> > >\n> > > CONFLICT (submodule): Merge conflict in <submodule>\n> > > Recursive merging with submodules is currently not supported.\n> > > To manually complete the merge:\n> > >  - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit>\n> >\n> > Same issues as I mentioned above for the single-merge-resolution-found case.\n> >\n>\n> ditto\n>\n> > >  - come back to superproject, and `git add <submodule>` to record the above merge\n> >\n> > \"merge or update\", right?\n>\n> ditto\n>\n> > > +ret:\n> > > +       if (!ret) {\n> > > +               if (!csub) {\n> > > +                       CALLOC_ARRAY(csub, 1);\n> > > +               }\n> > > +               csub_item.oid = *result;\n> >\n> > Shouldn't this be \"*b\" rather than \"*result\"?\n>\n> Yes it should\n>\n> >\n> > Also, are we risking telling the user to \"merge commit\n> > 0000000000000000000000000000000000000000\" from the submodule, given\n> > some of the early exits that you redirected to come through here?\n>\n> You are correct, but I'm not quite sure what should happen in this case. What does it mean for either o, a, or b to be null? Did a submodule get deleted on one side?\n\no is the version in the merge-base, a is the version from the first\nparent (thus the submodule version stored within HEAD), and b is the\nversion from the second parent (thus the submodule version stored\nwithin the main module's MERGE_HEAD commit).\n\nWe can't have all three be null, because that would just mean there\nwas never a submodule at this path.\n\nWe can't have two of the three be null, because that would either be\ndeleted on both sides, or added on one side, and those cases are\ntrivially resolvable elsewhere in the merge machinery and there's no\nneed to call merge_submodule().\n\nIf o is null, the submodule didn't exist in the merge base.  So it\nmust be added on both sides (and the two sides have to have different\nsubmodule commits, or the merge of the submodule would be trivially\nhandle-able elsewhere).\n\nIf a is null, it didn't exist in HEAD, meaning it was deleted on our\nside of history.  (And b has to be different than o, or else this\nwould be trivially resolvable as a deletion.)\n\nIf b is null, then it's similar to the above case, but the submodule\nwas deleted on the other side of history that we are trying to merge\ninto HEAD rather than on HEAD's side of history.\n\nHowever, now that I've said this, I took another look through the\ncode.  I think this actually isn't relevant right now.\nmerge_submodule() is only called from handle_content_merge(), which in\nturn is only called from two places: (1) process_renames(), and (2)\nthe filemask >= 6 section of process_entry().  The process_renames()\ncases, since we can't detect renames involving submodules, can't\ninvolve a case with a null oid for a submodule.  And the filemask >= 6\nimplies that only o could have a null hash.  That means the checks for\na and b being null oids within merge_submodule will never trigger.\nAnd we don't actually run the risk of telling users to merge the all\nnull commit.\n\nAny time we have a modify/delete issue with submodules, we'll just end\nup at the codepath that reports \"CONFLICT (modify/delete): ...\", and\nwhich doesn't mention anything about the path being a submodule, but\nit really doesn't need to; the text is accurate regardless.\n\n\n> > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n> >\n> > Maybe just make this function be static?\n>\n> It should be static now that this won't be called in merge-recursive\n>\n> > > +                                       find_unique_abbrev(&id, DEFAULT_ABBREV));\n> >\n> > Shouldn't this be repo_find_unique_abbrev(), and in particular with\n> > the submodule repository being passed to it?  Or perhaps using the\n> > format_commit() function, since merge_submodule() is already using it\n> > for the earlier submodule-related messages?\n>\n> It should and I will go with the format_commit() option so I don't have to pass subrepo information into the print function.\n>\n> > >  void merge_switch_to_result(struct merge_options *opt,\n> > >                             struct tree *head,\n> > >                             struct merge_result *result,\n> > > @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt,\n> > >                 }\n> > >                 string_list_clear(&olist, 0);\n> > >\n> > > +               print_submodule_conflict_suggestion(opt->conflicted_submodules);\n> > > +\n> > >                 /* Also include needed rename limit adjustment now */\n> > >                 diff_warn_rename_limit(\"merge.renamelimit\",\n> > >                                        opti->renames.needed_limit, 0);\n> > > diff --git a/merge-recursive.c b/merge-recursive.c\n> > > index fd1bbde061..311cc37886 100644\n> > > --- a/merge-recursive.c\n> > > +++ b/merge-recursive.c\n> >\n> > Is it worth updating merge-recursive.c here?  I'd rather only give it\n> > security fix updates until we delete it.\n>\n> Ah wasn't aware that was the status of merge-recursive. Will delete.\n>\n> > > diff --git a/merge-recursive.h b/merge-recursive.h\n> > > index b88000e3c2..b615955fb7 100644\n> > > --- a/merge-recursive.h\n> > > +++ b/merge-recursive.h\n> > > @@ -9,6 +9,7 @@ struct object_id;\n> > >  struct repository;\n> > >  struct tree;\n> > >\n> > > +struct conflicted_submodule_list;\n> > >  struct merge_options_internal;\n> > >  struct merge_options {\n> > >         struct repository *repo;\n> > > @@ -51,6 +52,21 @@ struct merge_options {\n> > >\n> > >         /* internal fields used by the implementation */\n> > >         struct merge_options_internal *priv;\n> > > +\n> > > +       /* field that holds submodule conflict information */\n> > > +       struct conflicted_submodule_list *conflicted_submodules;\n> >\n> > I think this should be added to merge_options_internal rather than to\n> > merge_options.  There's no need for an API caller to make use of\n> > these.\n> >\n> > Also, if a clear need arises later for API callers to make use of this\n> > information, then it should be part of merge_result for merge-ort.c,\n> > not part of merge_options.\n>\n> ack\n>\n> > > +};\n> > > +\n> > > +struct conflicted_submodule_item {\n> > > +       struct object_id oid;\n> > > +       char *path;\n> > > +       int resolution_exists;\n> > > +};\n> > > +\n> > > +struct conflicted_submodule_list {\n> > > +       struct conflicted_submodule_item *items;\n> > > +       size_t nr;\n> > > +       size_t alloc;\n> > >  };\n> >\n> > Similarly, I think these should be defined in merge-ort.c (and maybe\n> > also merge-recursive.c) near struct merge_options_internal.\n>\n> ack\n>\n> > >  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> > > @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt,\n> > >                             const struct object_id **merge_bases,\n> > >                             struct commit **result);\n> > >\n> > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub);\n> >\n> > and I think there's no reason to put this in the header file; it\n> > should just be a static function in merge-ort.c.  (And, if we really\n> > want to update merge-recursive.c, then copy the function over there.\n> > *Nothing* in merge-ort.c should call a function in merge-recursive.c\n> > and similarly nothing in merge-recursive.c should call any function in\n> > merge-ort.c.  Yes, that implies some duplication.  There is a fair\n> > amount already and we've discussed it, and chosen against creating a\n> > shared module as well.  I want merge-recursive to not be broken by\n> > merge-ort updates; it should remain stable until the day we finally\n> > get to nuke it.  I'm personally of the opinion that merge-recursive\n> > should only get security fixes at this point, though I haven't pinged\n> > to see if other folks agree with that point of view yet or not.  I'm\n> > not wasting my time fixing/improving it, though...)\n>\n> Good to know going forward I should only update merge-ort (unless for security fixes). I'll keep this in mind refactoring my patch.\n>\n> > Despite my many nitpicks and whatnot, it looks like your change will\n> > make things nicer for the user, and I think your patch is coming along\n> > nicely.  There are some things to fix up, but the overall direction\n> > seems good.\n>\n> Thank you for all of the helpful feedback!\n"},{"id":"458926","messageId":"20220712231935.2979727-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220629224059.1016645-1-calvinwan@google.com","subject":"[PATCH v4] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-12T23:19:35Z","receivedAt":"2022-07-12T23:19:48Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Apologies to any reviewers for the late v4 -- was OOO\n\nChanges since v3:\nThank you again Elijah for the helpful feedback! I have removed any code\ntouching merge-recursive.c, and refactored the rest into merge-ort.c.\nThe error message has been updated as well as any relevant test cases. I\nhad added a jump in v3 to \"ret:\" in merge_submodule() to accomodate\nearly returns, but this has been proven to not be necessary since an\nearly return means the submodule was either renamed or deleted, and this\ncase is already taken care of with the message \"CONFLICT (modify/delete):\"\n\n== Description ==\n\nWhen attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded or trivially resolved, the merge\nfails and git prints the following error:\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; fix conflicts and then commit the result.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers. \n\nUpdate error message to match the steps above. If git does not detect a \nmerge resolution, the following is printed:\n\n====\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n====\n\nIf git detects a possible merge resolution, the following is printed:\n\n====\n\nFailed to merge submodule sub, but a possible merge resolution exists:\n    <commit> Merge branch '<branch1>' into <branch2>\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\nTo manually complete the merge:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\nsuch as one listed above\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n====\n\nIf git detects multiple possible merge resolutions, the following is printed:\n\n====\n\nFailed to merge submodule sub, but multiple possible merges exist:\n    <commit> Merge branch '<branch1>' into <branch2>\n    <commit> Merge branch '<branch1>' into <branch3>\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\nTo manually complete the merge:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\nsuch as one listed above\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n== Previous Changes ==\n\nChanges since v2:\nThere are three major changes in this patch thanks to all the helpful\nfeedback! I have moved the print function from builtin/merge.c to\nmerge-ort.c and added it to merge_finalize() in merge-recursive.c and\nmerge_switch_to_result() in merge-ort.c. This allows other merge\nmachinery commands besides merge to print submodule conflict advice.\n\nI have moved the check for submodule conflicts from process_entry() to \nmerge_submodule(). This also required removing the early returns and\ninstead going to my submodule conflict check allowing us to pass back\ninformation on whether git detected a possible merge resolution or not.\n\nI have also updated the error message to better reflect the merge\nstatus. Specifically, if git detects a possible merge resolution, the\nerror message now also suggest updating to one of those resolutions.\n\nOther small changes: \n - Moved fields that hold submodule conflict information to new object\n - Shortened printed commit id to DEFAULT_ABBREV\n - Added a test to t6437-submodule-merge.sh for merges with no\n   resolution existence\n - Modified a test in t7402-submodule-rebase to show error message\n   prints in other parts of the merge machinery\n\nChanges since v1:\n - Removed advice to abort merge\n - Error message updated to contain more commit/submodule information\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\n merge-ort.c                 | 56 +++++++++++++++++++++++++++++++++++++\n t/t6437-submodule-merge.sh  | 31 +++++++++++++++++---\n t/t7402-submodule-rebase.sh |  6 ++--\n 3 files changed, 87 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0d3f42592f..872d924f58 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -292,6 +292,18 @@ struct rename_info {\n \tint needed_limit;\n };\n \n+struct conflicted_submodule_item {\n+\tchar *oid;\n+\tchar *path;\n+\tint resolution_exists;\n+};\n+\n+struct conflicted_submodule_list {\n+\tstruct conflicted_submodule_item *items;\n+\tsize_t nr;\n+\tsize_t alloc;\n+};\n+\n struct merge_options_internal {\n \t/*\n \t * paths: primary data structure in all of merge ort.\n@@ -385,6 +397,9 @@ struct merge_options_internal {\n \n \t/* call_depth: recursion level counter for merging merge bases */\n \tint call_depth;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct conflicted_submodule_list conflicted_submodules;\n };\n \n struct version_info {\n@@ -1610,6 +1625,9 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n+\tstruct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules;\n+\tstruct conflicted_submodule_item csub_item;\n+\tint resolution_exists = 0;\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n@@ -1703,6 +1721,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   \"which will accept this suggestion.\\n\"),\n \t\t\t oid_to_hex(&merges.objects[0].item->oid), path);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t\tbreak;\n \tdefault:\n \t\tfor (i = 0; i < merges.nr; i++)\n@@ -1712,10 +1731,22 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t _(\"Failed to merge submodule %s, but multiple \"\n \t\t\t   \"possible merges exist:\\n%s\"), path, sb.buf);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t}\n \n \tobject_array_clear(&merges);\n cleanup:\n+\tif (!ret) {\n+\t\tif (!csub) {\n+\t\t\tCALLOC_ARRAY(csub, 1);\n+\t\t}\n+\t\tcsub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n+\t\tcsub_item.path = xstrdup(path);\n+\t\tcsub_item.resolution_exists = resolution_exists;\n+\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n+\t\tcsub->items[csub->nr++] = csub_item;\n+\t\topt->priv->conflicted_submodules = *csub;\n+\t}\n \trepo_clear(&subrepo);\n \treturn ret;\n }\n@@ -4256,6 +4287,29 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n+\tif (csub && csub->nr > 0) {\n+\t\tint i;\n+\t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n+\t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n+\t\t\t\"This can be accomplished with the following steps:\\n\"));\n+\t\tfor (i = 0; i < csub->nr; i++) {\n+\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n+\t\t\t\t    \"or update to an existing commit which has merged those changes\\n\"),\n+\t\t\t\t\tcsub->items[i].path,\n+\t\t\t\t\tcsub->items[i].oid);\n+\t\t\tif (csub->items[i].resolution_exists)\n+\t\t\t\tprintf(_(\"such as one listed above\\n\"));\n+\t\t}\n+\t\tprintf(_(\" - come back to superproject, and `git add\"));\n+\t\tfor (i = 0; i < csub->nr; i++)\n+\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n+\t\tprintf(_(\"` to record the above merge or update \\n\"\n+\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n+\t\t\t\" - commit the resulting index in the superproject\\n\"));\n+\t}\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4324,6 +4378,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t}\n \t\tstring_list_clear(&olist, 0);\n \n+\t\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\n \t\t/* Also include needed rename limit adjustment now */\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opti->renames.needed_limit, 0);\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 178413c22f..cc1a312661 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n+\n+test_expect_success 'merging should conflict for non fast-forward' '\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi &&\n+\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t grep \"$sub_expect\" actual &&\n+\t git reset --hard)\n+'\n \n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n@@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n \t grep $(cat expect) actual > /dev/null &&\n+\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t grep \"$sub_expect\" actual &&\n \t git reset --hard)\n '\n \n@@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t fi &&\n \tgrep $(cat expect1) actual > /dev/null &&\n \tgrep $(cat expect2) actual > /dev/null &&\n+\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\tgrep \"$sub_expect\" actual &&\n \tgit reset --hard)\n '\n \n@@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\tgrep \"$sub_expect\" actual)\n '\n \n \ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..f1bb29681f 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n+\tgrep \"$sub_expect\" actual_output\n '\n \n test_done\n\nbase-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n-- \n2.37.0.144.g8ac04bfd2-goog\n\n"},{"id":"458999","messageId":"xmqqbktsdgro.fsf@gitster.g","threadId":"57963","inReplyTo":"20220712231935.2979727-1-calvinwan@google.com","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-13T18:11:39Z","receivedAt":"2022-07-13T18:11:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> Changes since v3:\n> Thank you again Elijah for the helpful feedback! I have removed any code\n> touching merge-recursive.c, and refactored the rest into merge-ort.c.\n> The error message has been updated as well as any relevant test cases. I\n> had added a jump in v3 to \"ret:\" in merge_submodule() to accomodate\n> early returns, but this has been proven to not be necessary since an\n> early return means the submodule was either renamed or deleted, and this\n> case is already taken care of with the message \"CONFLICT (modify/delete):\"\n\nUnfortunately a34edae6 (merge-ort: split out a separate\ndisplay_update_messages() function, 2022-06-18) moves the code this\npatch touches to a separate function.\n\nIf this is now an ort-specific topic by dropping merge-recursive\nsupport, perhaps we'd need to coordinate with the other branch.\n\nI think en/merge-tree is ready to be merged down to 'master' in a\nfew days, so I'll wait for [v5] of this patch after that happens.\n\nThanks.\n"},{"id":"459142","messageId":"7roq6p2s-17rn-477p-7n60-5p10926on785@tzk.qr","threadId":"57963","inReplyTo":"20220712231935.2979727-1-calvinwan@google.com","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-07-15T12:57:34Z","receivedAt":"2022-07-15T12:58:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Calvin,\n\nOn Tue, 12 Jul 2022, Calvin Wan wrote:\n\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index 178413c22f..cc1a312661 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n>  \t echo \"file-c\" > file-c &&\n>  \t git add file-c &&\n>  \t git commit -m \"sub-c\") &&\n> -\tgit commit -a -m \"c\" &&\n> +\tgit commit -a -m \"c\")\n> +'\n> +\n> +test_expect_success 'merging should conflict for non fast-forward' '\n> +\t(cd merge-search &&\n> +\t git checkout -b test-nonforward-a b &&\n> +\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +\t  then\n> +\t\ttest_must_fail git merge c >actual\n> +\t  else\n> +\t\ttest_must_fail git merge c 2> actual\n> +\t  fi &&\n> +\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +\t grep \"$sub_expect\" actual &&\n\nNo matter how hard I tried to stare at the code, no matter whether I\nlooked at `cw/submodule-merge-messages` or `seen`, I cannot see how this\n`grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only\nthe `ort` code has been taught this new trick.\n\nIn fact, when I run this locally with\n`GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here:\nhttps://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833\nand here:\nhttps://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820),\nit also leaves no error message in `actual`.\n\nSo you probably want to move the `sub_expect` assignment and the `grep`\ninto the `ort` clause of the conditional above.\n\nWith this fixup, things seem to work over here:\n\n-- snip --\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex cc1a3126619..3892d0bf742 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t git checkout -b test-nonforward-a b &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n-\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n-\t grep \"$sub_expect\" actual &&\n \t git reset --hard)\n '\n\n@@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex\n \t  git rev-parse sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n \t grep $(cat expect) actual > /dev/null &&\n-\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n-\t grep \"$sub_expect\" actual &&\n \t git reset --hard)\n '\n\n@@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t ) &&\n \t if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t else\n \t\ttest_must_fail git merge c 2> actual\n \t fi &&\n \tgrep $(cat expect1) actual > /dev/null &&\n \tgrep $(cat expect2) actual > /dev/null &&\n-\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n-\tgrep \"$sub_expect\" actual &&\n \tgit reset --hard)\n '\n\n@@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' '\n\n \tgit checkout -b test-backward e &&\n \ttest_must_fail git merge f >actual &&\n-\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n-\tgrep \"$sub_expect\" actual)\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\tthen\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n '\n\n-- snap --\n\n> +\t git reset --hard)\n\nIt would be better to move this before the subshell like this:\n\n\ttest_when_finished \"git -C merge-search reset --hard\" &&\n\nCiao,\nDscho\n\n> +'\n>\n> +test_expect_success 'finish setup for merge-search' '\n> +\t(cd merge-search &&\n>  \tgit checkout -b d a &&\n>  \t(cd sub &&\n>  \t git checkout -b sub-d sub-b &&\n> @@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n>  \t test_cmp expect actual)\n>  '\n>\n> -test_expect_success 'merging should conflict for non fast-forward' '\n> +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n>  \t(cd merge-search &&\n> -\t git checkout -b test-nonforward b &&\n> +\t git checkout -b test-nonforward-b b &&\n>  \t (cd sub &&\n>  \t  git rev-parse sub-d > ../expect) &&\n>  \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> @@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>  \t\ttest_must_fail git merge c 2> actual\n>  \t  fi &&\n>  \t grep $(cat expect) actual > /dev/null &&\n> +\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +\t grep \"$sub_expect\" actual &&\n>  \t git reset --hard)\n>  '\n>\n> @@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>  \t fi &&\n>  \tgrep $(cat expect1) actual > /dev/null &&\n>  \tgrep $(cat expect2) actual > /dev/null &&\n> +\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +\tgrep \"$sub_expect\" actual &&\n>  \tgit reset --hard)\n>  '\n>\n> @@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>  \tgit commit -a -m \"f\" &&\n>\n>  \tgit checkout -b test-backward e &&\n> -\ttest_must_fail git merge f)\n> +\ttest_must_fail git merge f >actual &&\n> +\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> +\tgrep \"$sub_expect\" actual)\n>  '\n>\n>\n> diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\n> index 8e32f19007..f1bb29681f 100755\n> --- a/t/t7402-submodule-rebase.sh\n> +++ b/t/t7402-submodule-rebase.sh\n> @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n>  \ttest_tick &&\n>  \tgit commit -m fourth &&\n>\n> -\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n> +\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n>  \tgit ls-files -s submodule >actual &&\n>  \t(\n>  \t\tcd submodule &&\n> @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' '\n>  \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n>  \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n>  \t) >expect &&\n> -\ttest_cmp expect actual\n> +\ttest_cmp expect actual &&\n> +\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n> +\tgrep \"$sub_expect\" actual_output\n>  '\n>\n>  test_done\n>\n> base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n> --\n> 2.37.0.144.g8ac04bfd2-goog\n>\n>\n>\n"},{"id":"459181","messageId":"xmqqh73hy3td.fsf@gitster.g","threadId":"57963","inReplyTo":"7roq6p2s-17rn-477p-7n60-5p10926on785@tzk.qr","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-16T06:22:38Z","receivedAt":"2022-07-16T06:22:43Z","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>> +test_expect_success 'merging should conflict for non fast-forward' '\n>> +\t(cd merge-search &&\n>> +\t git checkout -b test-nonforward-a b &&\n>> +\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>> +\t  then\n>> +\t\ttest_must_fail git merge c >actual\n>> +\t  else\n>> +\t\ttest_must_fail git merge c 2> actual\n>> +\t  fi &&\n>> +\t sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n>> +\t grep \"$sub_expect\" actual &&\n>\n> No matter how hard I tried to stare at the code, no matter whether I\n> looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this\n> `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only\n> the `ort` code has been taught this new trick.\n\nIndeed.  https://github.com/git/git/runs/7366982085 is how it failed\nin 'seen'.\n\nThanks for helping Calvin.\n"},{"id":"459218","messageId":"CABPp-BFq1wPQP4nYRfPCzHg00+nYWeN66zzQ8wxc-TXtnKTW+g@mail.gmail.com","threadId":"57963","inReplyTo":"7roq6p2s-17rn-477p-7n60-5p10926on785@tzk.qr","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-17T02:44:26Z","receivedAt":"2022-07-17T02:44:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 15, 2022 at 5:57 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Calvin,\n>\n> On Tue, 12 Jul 2022, Calvin Wan wrote:\n>\n> > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> > index 178413c22f..cc1a312661 100755\n> > --- a/t/t6437-submodule-merge.sh\n> > +++ b/t/t6437-submodule-merge.sh\n> > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n> >        echo \"file-c\" > file-c &&\n> >        git add file-c &&\n> >        git commit -m \"sub-c\") &&\n> > -     git commit -a -m \"c\" &&\n> > +     git commit -a -m \"c\")\n> > +'\n> > +\n> > +test_expect_success 'merging should conflict for non fast-forward' '\n> > +     (cd merge-search &&\n> > +      git checkout -b test-nonforward-a b &&\n> > +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> > +       then\n> > +             test_must_fail git merge c >actual\n> > +       else\n> > +             test_must_fail git merge c 2> actual\n> > +       fi &&\n> > +      sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> > +      grep \"$sub_expect\" actual &&\n>\n> No matter how hard I tried to stare at the code, no matter whether I\n> looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this\n> `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only\n> the `ort` code has been taught this new trick.\n\nRight, that's left over from an earlier version of the patch (which\ndid also modify merge-recursive).\n\n> In fact, when I run this locally with\n> `GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here:\n> https://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833\n> and here:\n> https://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820),\n> it also leaves no error message in `actual`.\n>\n> So you probably want to move the `sub_expect` assignment and the `grep`\n> into the `ort` clause of the conditional above.\n>\n> With this fixup, things seem to work over here:\n>\n> -- snip --\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index cc1a3126619..3892d0bf742 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>          git checkout -b test-nonforward-a b &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n> -        sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -        grep \"$sub_expect\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex\n>           git rev-parse sub-d > ../expect) &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n>          grep $(cat expect) actual > /dev/null &&\n> -        sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -        grep \"$sub_expect\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          ) &&\n>          if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>          then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>          else\n>                 test_must_fail git merge c 2> actual\n>          fi &&\n>         grep $(cat expect1) actual > /dev/null &&\n>         grep $(cat expect2) actual > /dev/null &&\n> -       sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -       grep \"$sub_expect\" actual &&\n>         git reset --hard)\n>  '\n>\n> @@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>\n>         git checkout -b test-backward e &&\n>         test_must_fail git merge f >actual &&\n> -       sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> -       grep \"$sub_expect\" actual)\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +       then\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> +               grep \"$sub_expect\" actual\n> +       fi)\n>  '\n>\n> -- snap --\n\nYeah, this looks good, thanks.\n"},{"id":"459219","messageId":"CABPp-BFHeobAdTz437CewmvbngCABTDeO4mUdHu0hGp=18UwMw@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqbktsdgro.fsf@gitster.g","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-17T02:46:05Z","receivedAt":"2022-07-17T02:46:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jul 13, 2022 at 11:11 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Calvin Wan <calvinwan@google.com> writes:\n>\n> > Changes since v3:\n> > Thank you again Elijah for the helpful feedback! I have removed any code\n> > touching merge-recursive.c, and refactored the rest into merge-ort.c.\n> > The error message has been updated as well as any relevant test cases. I\n> > had added a jump in v3 to \"ret:\" in merge_submodule() to accomodate\n> > early returns, but this has been proven to not be necessary since an\n> > early return means the submodule was either renamed or deleted, and this\n> > case is already taken care of with the message \"CONFLICT (modify/delete):\"\n>\n> Unfortunately a34edae6 (merge-ort: split out a separate\n> display_update_messages() function, 2022-06-18) moves the code this\n> patch touches to a separate function.\n>\n> If this is now an ort-specific topic by dropping merge-recursive\n> support, perhaps we'd need to coordinate with the other branch.\n>\n> I think en/merge-tree is ready to be merged down to 'master' in a\n> few days, so I'll wait for [v5] of this patch after that happens.\n\nYeah, sorry about that Calvin; one of the risks of working in the same\narea.  Should be pretty easy to resolve, though; let me know if you\nhave any questions when you go to re-roll.\n"},{"id":"459301","messageId":"CAFySSZD-GRwXvaK4sPJUOzU78GiJLunP7tZtF4i7nZ+f8i8WAg@mail.gmail.com","threadId":"57963","inReplyTo":"7roq6p2s-17rn-477p-7n60-5p10926on785@tzk.qr","subject":"Re: [PATCH v4] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-18T17:03:29Z","receivedAt":"2022-07-18T17:03:44Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> No matter how hard I tried to stare at the code, no matter whether I\n> looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this\n> `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only\n> the `ort` code has been taught this new trick.\n\nI definitely overlooked this removing the merge-recursive section of\nmy code. Thanks\nfor catching this!\n\nOn Fri, Jul 15, 2022 at 5:57 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Calvin,\n>\n> On Tue, 12 Jul 2022, Calvin Wan wrote:\n>\n> > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> > index 178413c22f..cc1a312661 100755\n> > --- a/t/t6437-submodule-merge.sh\n> > +++ b/t/t6437-submodule-merge.sh\n> > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n> >        echo \"file-c\" > file-c &&\n> >        git add file-c &&\n> >        git commit -m \"sub-c\") &&\n> > -     git commit -a -m \"c\" &&\n> > +     git commit -a -m \"c\")\n> > +'\n> > +\n> > +test_expect_success 'merging should conflict for non fast-forward' '\n> > +     (cd merge-search &&\n> > +      git checkout -b test-nonforward-a b &&\n> > +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> > +       then\n> > +             test_must_fail git merge c >actual\n> > +       else\n> > +             test_must_fail git merge c 2> actual\n> > +       fi &&\n> > +      sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> > +      grep \"$sub_expect\" actual &&\n>\n> No matter how hard I tried to stare at the code, no matter whether I\n> looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this\n> `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only\n> the `ort` code has been taught this new trick.\n>\n> In fact, when I run this locally with\n> `GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here:\n> https://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833\n> and here:\n> https://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820),\n> it also leaves no error message in `actual`.\n>\n> So you probably want to move the `sub_expect` assignment and the `grep`\n> into the `ort` clause of the conditional above.\n>\n> With this fixup, things seem to work over here:\n>\n> -- snip --\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index cc1a3126619..3892d0bf742 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' '\n>          git checkout -b test-nonforward-a b &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n> -        sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -        grep \"$sub_expect\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex\n>           git rev-parse sub-d > ../expect) &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n>          grep $(cat expect) actual > /dev/null &&\n> -        sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -        grep \"$sub_expect\" actual &&\n>          git reset --hard)\n>  '\n>\n> @@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          ) &&\n>          if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>          then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>          else\n>                 test_must_fail git merge c 2> actual\n>          fi &&\n>         grep $(cat expect1) actual > /dev/null &&\n>         grep $(cat expect2) actual > /dev/null &&\n> -       sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> -       grep \"$sub_expect\" actual &&\n>         git reset --hard)\n>  '\n>\n> @@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>\n>         git checkout -b test-backward e &&\n>         test_must_fail git merge f >actual &&\n> -       sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> -       grep \"$sub_expect\" actual)\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +       then\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> +               grep \"$sub_expect\" actual\n> +       fi)\n>  '\n>\n> -- snap --\n>\n> > +      git reset --hard)\n>\n> It would be better to move this before the subshell like this:\n>\n>         test_when_finished \"git -C merge-search reset --hard\" &&\n>\n> Ciao,\n> Dscho\n>\n> > +'\n> >\n> > +test_expect_success 'finish setup for merge-search' '\n> > +     (cd merge-search &&\n> >       git checkout -b d a &&\n> >       (cd sub &&\n> >        git checkout -b sub-d sub-b &&\n> > @@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n> >        test_cmp expect actual)\n> >  '\n> >\n> > -test_expect_success 'merging should conflict for non fast-forward' '\n> > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n> >       (cd merge-search &&\n> > -      git checkout -b test-nonforward b &&\n> > +      git checkout -b test-nonforward-b b &&\n> >        (cd sub &&\n> >         git rev-parse sub-d > ../expect) &&\n> >         if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> > @@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' '\n> >               test_must_fail git merge c 2> actual\n> >         fi &&\n> >        grep $(cat expect) actual > /dev/null &&\n> > +      sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> > +      grep \"$sub_expect\" actual &&\n> >        git reset --hard)\n> >  '\n> >\n> > @@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n> >        fi &&\n> >       grep $(cat expect1) actual > /dev/null &&\n> >       grep $(cat expect2) actual > /dev/null &&\n> > +     sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> > +     grep \"$sub_expect\" actual &&\n> >       git reset --hard)\n> >  '\n> >\n> > @@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' '\n> >       git commit -a -m \"f\" &&\n> >\n> >       git checkout -b test-backward e &&\n> > -     test_must_fail git merge f)\n> > +     test_must_fail git merge f >actual &&\n> > +     sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> > +     grep \"$sub_expect\" actual)\n> >  '\n> >\n> >\n> > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\n> > index 8e32f19007..f1bb29681f 100755\n> > --- a/t/t7402-submodule-rebase.sh\n> > +++ b/t/t7402-submodule-rebase.sh\n> > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n> >       test_tick &&\n> >       git commit -m fourth &&\n> >\n> > -     test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n> > +     test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n> >       git ls-files -s submodule >actual &&\n> >       (\n> >               cd submodule &&\n> > @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' '\n> >               echo \"160000 $(git rev-parse HEAD^^) 2  submodule\" &&\n> >               echo \"160000 $(git rev-parse HEAD) 3    submodule\"\n> >       ) >expect &&\n> > -     test_cmp expect actual\n> > +     test_cmp expect actual &&\n> > +     sub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n> > +     grep \"$sub_expect\" actual_output\n> >  '\n> >\n> >  test_done\n> >\n> > base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\n> > --\n> > 2.37.0.144.g8ac04bfd2-goog\n> >\n> >\n> >\n"},{"id":"459334","messageId":"20220718214349.3379328-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220712231935.2979727-1-calvinwan@google.com","subject":"[PATCH v5] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-18T21:43:49Z","receivedAt":"2022-07-18T21:44:14Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Changes since v4:\n - Rebased onto gitster/master\n - Fixed test cases to work with only merge-ort (and not merge-recursive)\n\n== Description ==\n\nWhen attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded or trivially resolved, the merge\nfails and git prints the following error:\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nAutomatic merge failed; fix conflicts and then commit the result.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers. \n\nUpdate error message to match the steps above. If git does not detect a \nmerge resolution, the following is printed:\n\n====\n\nFailed to merge submodule <submodule>\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n====\n\nIf git detects a possible merge resolution, the following is printed:\n\n====\n\nFailed to merge submodule sub, but a possible merge resolution exists:\n    <commit> Merge branch '<branch1>' into <branch2>\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\nTo manually complete the merge:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\nsuch as one listed above\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n====\n\nIf git detects multiple possible merge resolutions, the following is printed:\n\n====\n\nFailed to merge submodule sub, but multiple possible merges exist:\n    <commit> Merge branch '<branch1>' into <branch2>\n    <commit> Merge branch '<branch1>' into <branch3>\n\nCONFLICT (submodule): Merge conflict in <submodule>\nRecursive merging with submodules currently only supports trivial cases.\nPlease manually handle the merging of each conflicted submodule.\nThis can be accomplished with the following steps:\nTo manually complete the merge:\n - go to submodule (<submodule>), and either merge commit <commit>\nor update to an existing commit which has merged those changes\nsuch as one listed above\n - come back to superproject, and `git add <submodule>` to record the above merge or update\n - resolve any other conflicts in the superproject\n - commit the resulting index in the superproject\nAutomatic merge failed; fix conflicts and then commit the result.\n\n== Previous Changes ==\n\nChanges since v3:\nThank you again Elijah for the helpful feedback! I have removed any code\ntouching merge-recursive.c, and refactored the rest into merge-ort.c.\nThe error message has been updated as well as any relevant test cases. I\nhad added a jump in v3 to \"ret:\" in merge_submodule() to accomodate\nearly returns, but this has been proven to not be necessary since an\nearly return means the submodule was either renamed or deleted, and this\ncase is already taken care of with the message \"CONFLICT (modify/delete):\"\n\nChanges since v2:\nThere are three major changes in this patch thanks to all the helpful\nfeedback! I have moved the print function from builtin/merge.c to\nmerge-ort.c and added it to merge_finalize() in merge-recursive.c and\nmerge_switch_to_result() in merge-ort.c. This allows other merge\nmachinery commands besides merge to print submodule conflict advice.\n\nI have moved the check for submodule conflicts from process_entry() to \nmerge_submodule(). This also required removing the early returns and\ninstead going to my submodule conflict check allowing us to pass back\ninformation on whether git detected a possible merge resolution or not.\n\nI have also updated the error message to better reflect the merge\nstatus. Specifically, if git detects a possible merge resolution, the\nerror message now also suggest updating to one of those resolutions.\n\nOther small changes: \n - Moved fields that hold submodule conflict information to new object\n - Shortened printed commit id to DEFAULT_ABBREV\n - Added a test to t6437-submodule-merge.sh for merges with no\n   resolution existence\n - Modified a test in t7402-submodule-rebase to show error message\n   prints in other parts of the merge machinery\n\nChanges since v1:\n - Removed advice to abort merge\n - Error message updated to contain more commit/submodule information\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\n merge-ort.c                 | 56 +++++++++++++++++++++++++++++++++++++\n t/t6437-submodule-merge.sh  | 38 +++++++++++++++++++++----\n t/t7402-submodule-rebase.sh |  9 ++++--\n 3 files changed, 95 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 01f150ef3b..125ee3c0d1 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -292,6 +292,18 @@ struct rename_info {\n \tint needed_limit;\n };\n \n+struct conflicted_submodule_item {\n+\tchar *oid;\n+\tchar *path;\n+\tint resolution_exists;\n+};\n+\n+struct conflicted_submodule_list {\n+\tstruct conflicted_submodule_item *items;\n+\tsize_t nr;\n+\tsize_t alloc;\n+};\n+\n struct merge_options_internal {\n \t/*\n \t * paths: primary data structure in all of merge ort.\n@@ -387,6 +399,9 @@ struct merge_options_internal {\n \n \t/* call_depth: recursion level counter for merging merge bases */\n \tint call_depth;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct conflicted_submodule_list conflicted_submodules;\n };\n \n struct version_info {\n@@ -1741,6 +1756,9 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n+\tstruct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules;\n+\tstruct conflicted_submodule_item csub_item;\n+\tint resolution_exists = 0;\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n@@ -1835,6 +1853,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t   \"resolution exists: %s\"),\n \t\t\t path, sb.buf);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t\tbreak;\n \tdefault:\n \t\tfor (i = 0; i < merges.nr; i++)\n@@ -1845,10 +1864,22 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t _(\"Failed to merge submodule %s, but multiple \"\n \t\t\t   \"possible merges exist:\\n%s\"), path, sb.buf);\n \t\tstrbuf_release(&sb);\n+\t\tresolution_exists = 1;\n \t}\n \n \tobject_array_clear(&merges);\n cleanup:\n+\tif (!ret) {\n+\t\tif (!csub) {\n+\t\t\tCALLOC_ARRAY(csub, 1);\n+\t\t}\n+\t\tcsub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n+\t\tcsub_item.path = xstrdup(path);\n+\t\tcsub_item.resolution_exists = resolution_exists;\n+\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n+\t\tcsub->items[csub->nr++] = csub_item;\n+\t\topt->priv->conflicted_submodules = *csub;\n+\t}\n \trepo_clear(&subrepo);\n \treturn ret;\n }\n@@ -4412,6 +4443,29 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n+\tif (csub && csub->nr > 0) {\n+\t\tint i;\n+\t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n+\t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n+\t\t\t\"This can be accomplished with the following steps:\\n\"));\n+\t\tfor (i = 0; i < csub->nr; i++) {\n+\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n+\t\t\t\t    \"or update to an existing commit which has merged those changes\\n\"),\n+\t\t\t\t\tcsub->items[i].path,\n+\t\t\t\t\tcsub->items[i].oid);\n+\t\t\tif (csub->items[i].resolution_exists)\n+\t\t\t\tprintf(_(\"such as one listed above\\n\"));\n+\t\t}\n+\t\tprintf(_(\" - come back to superproject, and `git add\"));\n+\t\tfor (i = 0; i < csub->nr; i++)\n+\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n+\t\tprintf(_(\"` to record the above merge or update \\n\"\n+\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n+\t\t\t\" - commit the resulting index in the superproject\\n\"));\n+\t}\n+}\n+\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result)\n@@ -4461,6 +4515,8 @@ void merge_display_update_messages(struct merge_options *opt,\n \t}\n \tstring_list_clear(&olist, 0);\n \n+\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t       opti->renames.needed_limit, 0);\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex c253bf759a..84913525cf 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n \n+test_expect_success 'merging should conflict for non fast-forward' '\n+\ttest_when_finished \"git -C merge-search reset --hard\" &&\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi)\n+'\n+\n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse --short sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t ) &&\n \t if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t else\n \t\ttest_must_fail git merge c 2> actual\n \t fi &&\n@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n '\n \n \ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..ebeca12a71 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n+\t\tgrep \"$sub_expect\" actual_output\n+\tfi\n '\n \n test_done\n\nbase-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n-- \n2.37.0.170.g444d1eabd0-goog\n\n"},{"id":"459345","messageId":"xmqqzgh5d2s5.fsf@gitster.g","threadId":"57963","inReplyTo":"20220718214349.3379328-1-calvinwan@google.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-19T06:39:38Z","receivedAt":"2022-07-19T06:39:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> Changes since v4:\n>  - Rebased onto gitster/master\n>  - Fixed test cases to work with only merge-ort (and not merge-recursive)\n\nThat's not a log-message material.  You could have probably written\nscissors ...\n\n> == Description ==\n\n--- >8 ---\n\n... here, perhaps?\n\n> When attempting to merge in a superproject with conflicting submodule\n> pointers that cannot be fast-forwarded or trivially resolved, the merge\n> fails and git prints the following error:\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. merge submodules or update submodules to an already existing\n> \tcommit that reflects the merge\n>  2. add submodules changes to the superproject\n>  3. finish merging superproject\n> These steps are non-obvious for newer submodule users to figure out\n> based on the error message and neither `git submodule status` nor `git\n> status` provide any useful pointers. \n>\n> Update error message to match the steps above. If git does not detect a \n> merge resolution, the following is printed:\n>\n> ====\n>\n> Failed to merge submodule <submodule>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules currently only supports trivial cases.\n> Please manually handle the merging of each conflicted submodule.\n> This can be accomplished with the following steps:\n>  - go to submodule (<submodule>), and either merge commit <commit>\n> or update to an existing commit which has merged those changes\n>  - come back to superproject, and `git add <submodule>` to record the above merge or update\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> ====\n>\n> If git detects a possible merge resolution, the following is printed:\n>\n> ====\n>\n> Failed to merge submodule sub, but a possible merge resolution exists:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules currently only supports trivial cases.\n> Please manually handle the merging of each conflicted submodule.\n> This can be accomplished with the following steps:\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either merge commit <commit>\n> or update to an existing commit which has merged those changes\n> such as one listed above\n>  - come back to superproject, and `git add <submodule>` to record the above merge or update\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n>\n> ====\n>\n> If git detects multiple possible merge resolutions, the following is printed:\n>\n> ====\n>\n> Failed to merge submodule sub, but multiple possible merges exist:\n>     <commit> Merge branch '<branch1>' into <branch2>\n>     <commit> Merge branch '<branch1>' into <branch3>\n>\n> CONFLICT (submodule): Merge conflict in <submodule>\n> Recursive merging with submodules currently only supports trivial cases.\n> Please manually handle the merging of each conflicted submodule.\n> This can be accomplished with the following steps:\n> To manually complete the merge:\n>  - go to submodule (<submodule>), and either merge commit <commit>\n> or update to an existing commit which has merged those changes\n> such as one listed above\n>  - come back to superproject, and `git add <submodule>` to record the above merge or update\n>  - resolve any other conflicts in the superproject\n>  - commit the resulting index in the superproject\n> Automatic merge failed; fix conflicts and then commit the result.\n\nBut cutting the cruft at the top is still not enough, because the\nbelow are not log-message material, either.\n\nThese extraneous stuff may help reviewers while the patch is being\npolished, but once committed to our history, readers of \"git log\"\nshould not have to care what other attempts were made that did not\nbecome part of the final hstory.  The log message proper should be\nunderstandable standalone.\n\nThe stuff to help reviewers who may have seen earlier round are\nusually written in the cover letter, or after the three-dash line.\n\n> == Previous Changes ==\n>\n> Changes since v3:\n> ...\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> ---\n>  merge-ort.c                 | 56 +++++++++++++++++++++++++++++++++++++\n>  t/t6437-submodule-merge.sh  | 38 +++++++++++++++++++++----\n>  t/t7402-submodule-rebase.sh |  9 ++++--\n>  3 files changed, 95 insertions(+), 8 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 01f150ef3b..125ee3c0d1 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> ...\n> +\tif (csub && csub->nr > 0) {\n> +\t\tint i;\n> +\t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n> +\t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n> +\t\t\t\"This can be accomplished with the following steps:\\n\"));\n\nThis makes me wonder if these \"helpful but verbose\" messages should\nuse the advise mechanism.  Also, those reviewers who care about l10n\nmay suggest use of printf_ln() to lose the LF at the end of these\nmessages (i.e. not just the above one, but others we see below as\nwell).\n\n> +\t\tfor (i = 0; i < csub->nr; i++) {\n> +\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n> +\t\t\t\t    \"or update to an existing commit which has merged those changes\\n\"),\n> +\t\t\t\t\tcsub->items[i].path,\n> +\t\t\t\t\tcsub->items[i].oid);\n> +\t\t\tif (csub->items[i].resolution_exists)\n> +\t\t\t\tprintf(_(\"such as one listed above\\n\"));\n> +\t\t}\n> +\t\tprintf(_(\" - come back to superproject, and `git add\"));\n> +\t\tfor (i = 0; i < csub->nr; i++)\n> +\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n> +\t\tprintf(_(\"` to record the above merge or update \\n\"\n> +\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n> +\t\t\t\" - commit the resulting index in the superproject\\n\"));\n> +\t}\n> +}\n> +\n>  void merge_display_update_messages(struct merge_options *opt,\n>  \t\t\t\t   int detailed,\n>  \t\t\t\t   struct merge_result *result)\n> @@ -4461,6 +4515,8 @@ void merge_display_update_messages(struct merge_options *opt,\n>  \t}\n>  \tstring_list_clear(&olist, 0);\n>  \n> +\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n> +\n>  \t/* Also include needed rename limit adjustment now */\n>  \tdiff_warn_rename_limit(\"merge.renamelimit\",\n>  \t\t\t       opti->renames.needed_limit, 0);\n\nThanks.\n"},{"id":"459346","messageId":"xmqqsfmxd17s.fsf@gitster.g","threadId":"57963","inReplyTo":"20220718214349.3379328-1-calvinwan@google.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-19T07:13:27Z","receivedAt":"2022-07-19T07:13:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> @@ -1835,6 +1853,7 @@ static int merge_submodule(struct merge_options *opt,\n>  \t\t\t   \"resolution exists: %s\"),\n>  \t\t\t path, sb.buf);\n>  \t\tstrbuf_release(&sb);\n> +\t\tresolution_exists = 1;\n>  \t\tbreak;\n>  \tdefault:\n>  \t\tfor (i = 0; i < merges.nr; i++)\n> @@ -1845,10 +1864,22 @@ static int merge_submodule(struct merge_options *opt,\n>  \t\t\t _(\"Failed to merge submodule %s, but multiple \"\n>  \t\t\t   \"possible merges exist:\\n%s\"), path, sb.buf);\n>  \t\tstrbuf_release(&sb);\n> +\t\tresolution_exists = 1;\n>  \t}\n\nThese work on the result of calling find_first_merges(), but is it\npossible that we are asked to call this function more than once\nbecause we see conflicted submodule updates at two or more paths?\n\nI may be misreading the code, but find_first_merges(), either the\nversion we see in this file, or the one in merge-recursive.c, or its\noriginal introduced in 68d03e4a (Implement automatic fast-forward\nmerge for submodules, 2010-07-07), look safe to be called twice.  It\nruns the get_revision() machinery, smudging the object flags while\nwalking the history, but I do not see any code that cleans up these\nflags for the second traversal.\n\nAlso, this is not a new problem, but I am afraid that the logic to\nfind existing merges in find_first_merges() might be overly loose.\nIt tries to find existing merges that can reach the two commits, and\nthen finds, among these merges, the one that is not descendant of\nany other such merges.  Don't we end up finding a merge M\n\n    A---o---M\n           /\n          B\n\nwhen a superproject merge needs a merge of A and B in the submodule?\nThat is certainly a merge that contains both A and B and it may be\ncloser to A and B than any other existing merges, but it still may\nnot be a merge between A and B (in the depicted case, an extra\ncommit 'o' nobody ordered is included for free in the result).  I am\nnot seeing how existing code tries to avoid such a situation.\n\nThanks.\n"},{"id":"459426","messageId":"CAFySSZDoNX_5mxdKaT8JWkpYxBFxivQc_p2q_4bQk6YsT8QeZA@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqsfmxd17s.fsf@gitster.g","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-19T19:07:02Z","receivedAt":"2022-07-19T19:07:16Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> These work on the result of calling find_first_merges(), but is it\n> possible that we are asked to call this function more than once\n> because we see conflicted submodule updates at two or more paths?\n\nThis does get called multiple times if we see conflicted submodule\nupdates at two or more paths.\n\n> I may be misreading the code, but find_first_merges(), either the\n> version we see in this file, or the one in merge-recursive.c, or its\n> original introduced in 68d03e4a (Implement automatic fast-forward\n> merge for submodules, 2010-07-07), look safe to be called twice.  It\n> runs the get_revision() machinery, smudging the object flags while\n> walking the history, but I do not see any code that cleans up these\n> flags for the second traversal.\n\nI don't quite understand which flags need to be cleaned up for the\nsecond traversal.\n\n> Also, this is not a new problem, but I am afraid that the logic to\n> find existing merges in find_first_merges() might be overly loose.\n> It tries to find existing merges that can reach the two commits, and\n> then finds, among these merges, the one that is not descendant of\n> any other such merges.  Don't we end up finding a merge M\n>\n>     A---o---M\n>            /\n>           B\n>\n> when a superproject merge needs a merge of A and B in the submodule?\n> That is certainly a merge that contains both A and B and it may be\n> closer to A and B than any other existing merges, but it still may\n> not be a merge between A and B (in the depicted case, an extra\n> commit 'o' nobody ordered is included for free in the result).  I am\n> not seeing how existing code tries to avoid such a situation.\n\nIt is true that we find merge M and it isn't representative of a merge of A\nand B in the submodule. In this case, the existing code prints:\n\n\"Failed to merge submodule %s, but a possible merge resolution exists: %s\"\n\nWhile this part doesn't claim M to be a guaranteed merge resolution, my\nchange adds this line:\n\n\"or update to an existing commit which has merged those changes such as\none listed above\"\n\nInstead of adding more verbosity to this language, it seems like a better\nidea to remove \"such as one listed above\" entirely (and subsequently any\nof my code that flags merge resolutions).\n"},{"id":"459427","messageId":"CAFySSZDqMVV=Zv9hVa+S9=fhuvCTZvZUL50qMfSJHh4KTpm5RA@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqzgh5d2s5.fsf@gitster.g","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-19T19:30:13Z","receivedAt":"2022-07-19T19:30:29Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> The stuff to help reviewers who may have seen earlier round are\n> usually written in the cover letter, or after the three-dash line.\nAh I see since this patch doesn't have a cover letter, I should put\nall the reviewer-centric stuff after the three-dash line.\n\n> > +     if (csub && csub->nr > 0) {\n> > +             int i;\n> > +             printf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n> > +                     \"Please manually handle the merging of each conflicted submodule.\\n\"\n> > +                     \"This can be accomplished with the following steps:\\n\"));\n>\n> This makes me wonder if these \"helpful but verbose\" messages should\n> use the advise mechanism.\n\nI agree. The only loss of information if someone turned off this message\nwould be the commit id that possibly needs to be merged.\n\n> Also, those reviewers who care about l10n\n> may suggest use of printf_ln() to lose the LF at the end of these\n> messages (i.e. not just the above one, but others we see below as\n> well).\n\nack\n"},{"id":"459432","messageId":"xmqqfsiwamec.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZDqMVV=Zv9hVa+S9=fhuvCTZvZUL50qMfSJHh4KTpm5RA@mail.gmail.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-19T20:16:27Z","receivedAt":"2022-07-19T20:16:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n>> The stuff to help reviewers who may have seen earlier round are\n>> usually written in the cover letter, or after the three-dash line.\n> Ah I see since this patch doesn't have a cover letter, I should put\n> all the reviewer-centric stuff after the three-dash line.\n>\n>> > +     if (csub && csub->nr > 0) {\n>> > +             int i;\n>> > +             printf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n>> > +                     \"Please manually handle the merging of each conflicted submodule.\\n\"\n>> > +                     \"This can be accomplished with the following steps:\\n\"));\n>>\n>> This makes me wonder if these \"helpful but verbose\" messages should\n>> use the advise mechanism.\n>\n> I agree. The only loss of information if someone turned off this message\n> would be the commit id that possibly needs to be merged.\n\nIf the commit found by find_first_merges() are useful, then it is\nlosing information, so it is one argument against using the advise\nmechanism.\n"},{"id":"459433","messageId":"xmqq8rooalr1.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZDoNX_5mxdKaT8JWkpYxBFxivQc_p2q_4bQk6YsT8QeZA@mail.gmail.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-07-19T20:30:26Z","receivedAt":"2022-07-19T20:30:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n>> These work on the result of calling find_first_merges(), but is it\n>> possible that we are asked to call this function more than once\n>> because we see conflicted submodule updates at two or more paths?\n>\n> This does get called multiple times if we see conflicted submodule\n> updates at two or more paths.\n>\n>> I may be misreading the code, but find_first_merges(), either the\n>> version we see in this file, or the one in merge-recursive.c, or its\n>> original introduced in 68d03e4a (Implement automatic fast-forward\n>> merge for submodules, 2010-07-07), look safe to be called twice.  It\n>> runs the get_revision() machinery, smudging the object flags while\n>> walking the history, but I do not see any code that cleans up these\n>> flags for the second traversal.\n>\n> I don't quite understand which flags need to be cleaned up for the\n> second traversal.\n\nUNINTERESTING, TREESAME, ADDED, SEEN, SHOWN are among the flags used\nby the object walk (if MyFirstObjectWalk does not talk about them,\nit probably should), and they need to be cleared before you prepare\na new \"struct rev_info\" and throw it at setup_revisions(),\nprepare_revision_walk(), and start calling get_revision().\n\nsubmodule.c::collect_changed_submodules() has its own revision walk,\nbut it calls reset_revision_walk() to clear these flags from all\nobjects in the superproject (i.e. the_repository).\n\nI _think_ the reason why this never turned out to be a problem in\npractice is because we do not run this helper twice for the same\nsubmodule.  Even though we may smudge many objects from a submodule\nwith an object walk without clearing their flags, as long as we\nrun the next object walk in a different submodule whose object flags\nare still unsmudged, it would be OK.\n\n"},{"id":"459857","messageId":"220725.868rohragt.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"20220718214349.3379328-1-calvinwan@google.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-07-25T06:05:42Z","receivedAt":"2022-07-25T06:08:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 18 2022, Calvin Wan wrote:\n\n\nOne of the CI failures in \"seen\" is because of my topic to mark (among\nother things) t1500*.sh as passing with SANITIZE=leak, and this change.\n\nBecause...\n\n> ..\n>  \tobject_array_clear(&merges);\n>  cleanup:\n> +\tif (!ret) {\n> +\t\tif (!csub) {\n> +\t\t\tCALLOC_ARRAY(csub, 1);\n> +\t\t}\n> +\t\tcsub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n> +\t\tcsub_item.path = xstrdup(path);\n> +\t\tcsub_item.resolution_exists = resolution_exists;\n> +\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n\n... in \"cleanup\" we're ALLOC_GROW()-ing? I haven't looked into this yet,\nbut this seems susppect. This is line 1879 in the following stacktrace:\n\t\n\t+ git -C super merge branch1\n\tFailed to merge submodule dir/sub\n\tCONFLICT (submodule): Merge conflict in dir/sub\n\tRecursive merging with submodules currently only supports trivial cases.\n\tPlease manually handle the merging of each conflicted submodule.\n\tThis can be accomplished with the following steps:\n\t - go to submodule (dir/sub), and either merge commit 7018b5f\n\tor update to an existing commit which has merged those changes\n\t - come back to superproject, and `git add dir/sub` to record the above merge or update\n\t - resolve any other conflicts in the superproject\n\t - commit the resulting index in the superproject\n\tAutomatic merge failed; fix conflicts and then commit the result.\n\t\n\t=================================================================\n\t==31261==ERROR: LeakSanitizer: detected memory leaks\n\t\n\tDirect leak of 576 byte(s) in 1 object(s) allocated from:\n\t    #0 0x4565ad in __interceptor_realloc (git+0x4565ad)\n\t    #1 0x76ecfd in xrealloc wrapper.c:136:8\n\t    #2 0x64fcd3 in merge_submodule merge-ort.c:1879:3\n\t    #3 0x64ee9b in handle_content_merge merge-ort.c:2118:11\n\t    #4 0x651c14 in process_entry merge-ort.c:4056:17\n\t    #5 0x648c05 in process_entries merge-ort.c:4267:4\n\t    #6 0x646c03 in merge_ort_nonrecursive_internal merge-ort.c:4893:2\n\t    #7 0x6470f3 in merge_ort_internal merge-ort.c:4982:2\n\t    #8 0x646de0 in merge_incore_recursive merge-ort.c:5033:2\n\t    #9 0x652d1a in merge_ort_recursive merge-ort-wrappers.c:57:2\n\t    #10 0x4ec0f6 in try_merge_strategy builtin/merge.c:764:12\n\t    #11 0x4e9bf2 in cmd_merge builtin/merge.c:1710:9\n\t    #12 0x45a3aa in run_builtin git.c:466:11\n\t    #13 0x458e41 in handle_builtin git.c:720:3\n\t    #14 0x459d85 in run_argv git.c:787:4\n\t    #15 0x458bfa in cmd_main git.c:920:19\n\t    #16 0x56a049 in main common-main.c:56:11\n\t    #17 0x7fe592bca81c in __libc_start_main csu/../csu/libc-start.c:332:16\n\t    #18 0x431139 in _start (git+0x431139)\n"},{"id":"459883","messageId":"220725.86v8rlpdix.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"20220718214349.3379328-1-calvinwan@google.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-07-25T12:31:39Z","receivedAt":"2022-07-25T12:45:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 18 2022, Calvin Wan wrote:\n\nI'll have some more comments, but just on the output/i18n:\n\n> +\t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n> +\t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n> +\t\t\t\"This can be accomplished with the following steps:\\n\"));\n> +\t\tfor (i = 0; i < csub->nr; i++) {\n> +\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n> +\t\t\t\t    \"or update to an existing commit which has merged those changes\\n\"),\n\nThis seems to have some formatting issues, i.e. we'll emit things like:\n\n - one\none continued\n - tow\ntwo continued\n\nWhereas we want this, surely?;\n\n - one\n   one continued\n - two\n   two continued\n\nI.e. we're using \" - \" to mark list items, but then not indenting the\nitems.\n\n\n> +\t\t\t\t\tcsub->items[i].path,\n> +\t\t\t\t\tcsub->items[i].oid);\n> +\t\t\tif (csub->items[i].resolution_exists)\n> +\t\t\t\tprintf(_(\"such as one listed above\\n\"));\n\n\tthat people might read this backwards\n\tYou need to consider\n\n:)\n\nI.e. this is \"translation lego\" that we try to avoid.\n\nIt's a bit more verbose (but it often is, unfortunately)\n\nI think you can borrow a bit from ba5e8a0eb80 (object-name: make\nambiguous object output translatable, 2022-01-27) here, i.e.:\n\n 1. Just translate a message like \"go to submodule ...\\nor update to\n    an\", have another variant for the \"resolution exists\".\n\n 2. As translators retain those \\n split those lines and format them\n    with e.g. _(\" %s\") (you could borrow the string used in\n    object-name.c via ba5e8a0eb80), or _(\"- %s\").\n\n    This will give you list-itemized output, which will also format\n    correctly in RTL languages, and takes the formatting concerns\n    completely out of the hands of translators, and allows us to change\n    it later ourselves.\n\n    I.e. we can safely assume that for a \\n-delimited translation we can\n    take \\n-delimited input from a translator and treat the first line\n    specially as a \"first line in new list item\".\n\n> +\t\t}\n> +\t\tprintf(_(\" - come back to superproject, and `git add\"));\n> +\t\tfor (i = 0; i < csub->nr; i++)\n> +\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n\nMore inconsistent indentation, and per ba5e8a0eb80 you should explain\nany addition of magical formatting like \" %s\" with a TRANSLATORS\ncomment.\n\n\n> +\t\tprintf(_(\"` to record the above merge or update \\n\"\n> +\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n> +\t\t\t\" - commit the resulting index in the superproject\\n\"));\n\nA bit more odd formatting, i.e.:\n\n - A trailing  \" \" before a \\n?\n\n - \" \" after `, what is that ` doing? At first I thought it was a typo,\n   but looking again it's a continuation of `` from above\n\n   It's quite odd to tell a user to run a command with the `` syntax,\n   which is used for interpolation. Let's instead suggest:\n\n        blah blah blah run:\n\n\t\tgit add foo \\\n\t\t\tbar \\ [...]\n\t                baz\n\n   I.e. use \\ at the end of lines to note a multi-line command, not wrap\n   the whole thing in ``-quotes.\n\n - If a message must always end in a \\n just add it between _()'s,\n   instead of making it part of the _() string.\n"},{"id":"459884","messageId":"220725.86r129pd8m.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"220725.868rohragt.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-07-25T12:11:34Z","receivedAt":"2022-07-25T12:53:08Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 25 2022, Ævar Arnfjörð Bjarmason wrote:\n\n> On Mon, Jul 18 2022, Calvin Wan wrote:\n>\n>\n> One of the CI failures in \"seen\" is because of my topic to mark (among\n> other things) t1500*.sh as passing with SANITIZE=leak, and this change.\n>\n> Because...\n>\n>> ..\n>>  \tobject_array_clear(&merges);\n>>  cleanup:\n>> +\tif (!ret) {\n>> +\t\tif (!csub) {\n>> +\t\t\tCALLOC_ARRAY(csub, 1);\n>> +\t\t}\n>> +\t\tcsub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n>> +\t\tcsub_item.path = xstrdup(path);\n>> +\t\tcsub_item.resolution_exists = resolution_exists;\n>> +\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n>\n> ... in \"cleanup\" we're ALLOC_GROW()-ing? I haven't looked into this yet,\n> but this seems susppect. This is line 1879 in the following stacktrace:\n> \t\n> \t+ git -C super merge branch1\n> \tFailed to merge submodule dir/sub\n> \tCONFLICT (submodule): Merge conflict in dir/sub\n> \tRecursive merging with submodules currently only supports trivial cases.\n> \tPlease manually handle the merging of each conflicted submodule.\n> \tThis can be accomplished with the following steps:\n> \t - go to submodule (dir/sub), and either merge commit 7018b5f\n> \tor update to an existing commit which has merged those changes\n> \t - come back to superproject, and `git add dir/sub` to record the above merge or update\n> \t - resolve any other conflicts in the superproject\n> \t - commit the resulting index in the superproject\n> \tAutomatic merge failed; fix conflicts and then commit the result.\n> \t\n> \t=================================================================\n> \t==31261==ERROR: LeakSanitizer: detected memory leaks\n> \t\n> \tDirect leak of 576 byte(s) in 1 object(s) allocated from:\n> \t    #0 0x4565ad in __interceptor_realloc (git+0x4565ad)\n> \t    #1 0x76ecfd in xrealloc wrapper.c:136:8\n> \t    #2 0x64fcd3 in merge_submodule merge-ort.c:1879:3\n> \t    #3 0x64ee9b in handle_content_merge merge-ort.c:2118:11\n> \t    #4 0x651c14 in process_entry merge-ort.c:4056:17\n> \t    #5 0x648c05 in process_entries merge-ort.c:4267:4\n> \t    #6 0x646c03 in merge_ort_nonrecursive_internal merge-ort.c:4893:2\n> \t    #7 0x6470f3 in merge_ort_internal merge-ort.c:4982:2\n> \t    #8 0x646de0 in merge_incore_recursive merge-ort.c:5033:2\n> \t    #9 0x652d1a in merge_ort_recursive merge-ort-wrappers.c:57:2\n> \t    #10 0x4ec0f6 in try_merge_strategy builtin/merge.c:764:12\n> \t    #11 0x4e9bf2 in cmd_merge builtin/merge.c:1710:9\n> \t    #12 0x45a3aa in run_builtin git.c:466:11\n> \t    #13 0x458e41 in handle_builtin git.c:720:3\n> \t    #14 0x459d85 in run_argv git.c:787:4\n> \t    #15 0x458bfa in cmd_main git.c:920:19\n> \t    #16 0x56a049 in main common-main.c:56:11\n> \t    #17 0x7fe592bca81c in __libc_start_main csu/../csu/libc-start.c:332:16\n> \t    #18 0x431139 in _start (git+0x431139)\n\nLooking at this a bit more this fixes it, and also the NIH allocation\npattern & what we can do with a \"struct string_list\" instead of managing\nour own allocations.\n\nI did wonder why you need to allocate the \"oid\" at all, i.e. at the time\nof merge_submodules() can't we just squirrel away the \"const struct\nobject_id *\" and only when we emit the message later run\nrepo_find_unique_abbrev()?\n\nI think we can do that, but it means re-init-ing the submodule, and I'm\nnot sure if there's any edge cases with the state changing between us\ntrying merge_submodules() and the eventual messages we emit.\n\nBut it would mean that we'd just need to allocate a pointer to that oid\n+ resolution_exists.\n\nPerhaps we could also continue down that path and do this with just one\nallocation for that \"oid/resolution_exists\" side-data. I.e. you'd have a\nsecond string_list in the struct, have a non-NULL util mean \"resolution\nexists\", and the \"string\" there would be nodup'd (we could refer to the\n\"string\" in the first one).\n\nBut I think all of that probably isn't worth it, but the below seems\nlike a good trade-off & good place to stop, and fixes the memory leak\nthis topic introduces.\n\n merge-ort.c | 69 +++++++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 40 insertions(+), 29 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 154d33f2f45..5cf87f70b58 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -293,16 +293,17 @@ struct rename_info {\n };\n \n struct conflicted_submodule_item {\n-\tchar *oid;\n-\tchar *path;\n-\tint resolution_exists;\n+\tchar *abbrev;\n+\tint resolution_exists:1;\n };\n \n-struct conflicted_submodule_list {\n-\tstruct conflicted_submodule_item *items;\n-\tsize_t nr;\n-\tsize_t alloc;\n-};\n+static void conflicted_submodule_item_free(void *util, const char *str)\n+{\n+\tstruct conflicted_submodule_item *item = util;\n+\n+\tfree(item->abbrev);\n+\tfree(item);\n+}\n \n struct merge_options_internal {\n \t/*\n@@ -401,7 +402,7 @@ struct merge_options_internal {\n \tint call_depth;\n \n \t/* field that holds submodule conflict information */\n-\tstruct conflicted_submodule_list conflicted_submodules;\n+\tstruct string_list conflicted_submodules;\n };\n \n struct version_info {\n@@ -701,6 +702,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \n \tmem_pool_discard(&opti->pool, 0);\n \n+\tstring_list_clear_func(&opti->conflicted_submodules,\n+\t\t\t       conflicted_submodule_item_free);\n+\n \t/* Clean out callback_data as well. */\n \tFREE_AND_NULL(renames->callback_data);\n \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n@@ -1756,8 +1760,6 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct commit *commit_o, *commit_a, *commit_b;\n \tint parent_count;\n \tstruct object_array merges;\n-\tstruct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules;\n-\tstruct conflicted_submodule_item csub_item;\n \tint resolution_exists = 0;\n \n \tint i;\n@@ -1870,15 +1872,17 @@ static int merge_submodule(struct merge_options *opt,\n \tobject_array_clear(&merges);\n cleanup:\n \tif (!ret) {\n-\t\tif (!csub) {\n-\t\t\tCALLOC_ARRAY(csub, 1);\n-\t\t}\n-\t\tcsub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n-\t\tcsub_item.path = xstrdup(path);\n-\t\tcsub_item.resolution_exists = resolution_exists;\n-\t\tALLOC_GROW(csub->items, csub->nr + 1, csub->alloc);\n-\t\tcsub->items[csub->nr++] = csub_item;\n-\t\topt->priv->conflicted_submodules = *csub;\n+\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n+\t\tstruct conflicted_submodule_item *util;\n+\t\tconst char *abbrev;\n+\n+\t\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);;\n+\n+\t\tutil = xmalloc(sizeof(*util));\n+\t\tutil->abbrev = xstrdup(abbrev);\n+\t\tutil->resolution_exists = resolution_exists;\n+\n+\t\tstring_list_append(csub, path)->util = util;\n \t}\n \trepo_clear(&subrepo);\n \treturn ret;\n@@ -4465,23 +4469,26 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n-static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) {\n-\tif (csub && csub->nr > 0) {\n-\t\tint i;\n+static void print_submodule_conflict_suggestion(struct string_list *csub) {\n+\tif (csub->nr > 0) {\n+\t\tstruct string_list_item *item;\n+\n \t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n \t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n \t\t\t\"This can be accomplished with the following steps:\\n\"));\n-\t\tfor (i = 0; i < csub->nr; i++) {\n+\n+\t\tfor_each_string_list_item(item, csub) {\n+\t\t\tstruct conflicted_submodule_item *util = item->util;\n+\n \t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n \t\t\t\t    \"or update to an existing commit which has merged those changes\\n\"),\n-\t\t\t\t\tcsub->items[i].path,\n-\t\t\t\t\tcsub->items[i].oid);\n-\t\t\tif (csub->items[i].resolution_exists)\n+\t\t\t\t\titem->string, util->abbrev);\n+\t\t\tif (util->resolution_exists)\n \t\t\t\tprintf(_(\"such as one listed above\\n\"));\n \t\t}\n \t\tprintf(_(\" - come back to superproject, and `git add\"));\n-\t\tfor (i = 0; i < csub->nr; i++)\n-\t\t\tprintf(_(\" %s\"), csub->items[i].path);\n+\t\tfor_each_string_list_item(item, csub)\n+\t\t\tprintf(_(\" %s\"), item->string);\n \t\tprintf(_(\"` to record the above merge or update \\n\"\n \t\t\t\" - resolve any other conflicts in the superproject\\n\"\n \t\t\t\" - commit the resulting index in the superproject\\n\"));\n@@ -4538,6 +4545,8 @@ void merge_display_update_messages(struct merge_options *opt,\n \tstring_list_clear(&olist, 0);\n \n \tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\tstring_list_clear_func(&opti->conflicted_submodules,\n+\t\t\t       conflicted_submodule_item_free);\n \n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n@@ -4795,6 +4804,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n \t */\n \tstrmap_init(&opt->priv->conflicts);\n \n+\tstring_list_init_dup(&opt->priv->conflicted_submodules);\n+\n \ttrace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n }\n \n"},{"id":"459928","messageId":"CAFySSZD+kofhqYmxOnoNi49XSps6z44Ym8=qMBYjwOKJR3Mymw@mail.gmail.com","threadId":"57963","inReplyTo":"220725.86v8rlpdix.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-25T21:27:21Z","receivedAt":"2022-07-25T21:28:35Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Hi Ævar,\n\n> Whereas we want this, surely?;\n>\n>  - one\n>    one continued\n>  - two\n>    two continued\n>\n> I.e. we're using \" - \" to mark list items, but then not indenting the\n> items.\n\nYes we surely do!\n\n> I think you can borrow a bit from ba5e8a0eb80 (object-name: make\n> ambiguous object output translatable, 2022-01-27) here, i.e.:\n>\n>  1. Just translate a message like \"go to submodule ...\\nor update to\n>     an\", have another variant for the \"resolution exists\".\n\nI am removing the \"resolution exists\" variant because the text that\ncame with it was unclear to begin with.\n\n> per ba5e8a0eb80 you should explain\n> any addition of magical formatting like \" %s\" with a TRANSLATORS\n> comment.\n\nack\n\n>  - A trailing  \" \" before a \\n?\n\nack\n\n>    It's quite odd to tell a user to run a command with the `` syntax,\n>    which is used for interpolation. Let's instead suggest:\n>\n>         blah blah blah run:\n>\n>                 git add foo \\\n>                         bar \\ [...]\n>                         baz\n>\n>    I.e. use \\ at the end of lines to note a multi-line command, not wrap\n>    the whole thing in ``-quotes.\n\nThat looks much better than what I currently have.\n\n>  - If a message must always end in a \\n just add it between _()'s,\n>    instead of making it part of the _() string.\n\nack\n"},{"id":"459932","messageId":"CAFySSZDrXiiStn9BfKzxjXU-QsR6Bq1cWf9FQHTG-D9kUyHhng@mail.gmail.com","threadId":"57963","inReplyTo":"220725.86r129pd8m.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-25T22:03:49Z","receivedAt":"2022-07-25T22:04:04Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Hi Ævar,\n\nThanks in advance for the suggestions and catching my memory leak.\n\n> Perhaps we could also continue down that path and do this with just one\n> allocation for that \"oid/resolution_exists\" side-data. I.e. you'd have a\n> second string_list in the struct, have a non-NULL util mean \"resolution\n> exists\", and the \"string\" there would be nodup'd (we could refer to the\n> \"string\" in the first one).\n\nSince I am removing the \"resolution exists\" field, I modified your changes\nto use the util to only hold the abbrev.\n"},{"id":"460002","messageId":"20220726210020.3397249-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220718214349.3379328-1-calvinwan@google.com","subject":"[PATCH v6] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-26T21:00:20Z","receivedAt":"2022-07-26T21:00:35Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded or trivially resolved, the merge\nfails and git prints an error message that accurately describes the\nfailure, but does not provide steps for the user to resolve the error.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers.\n\nUpdate error message to provide steps to resolve submodule merge\nconflict. Future work could involve adding an advice flag to the\nmessage. Although the message is long, it also has the id of the \ncommit that needs to be merged, which could be useful information\nfor the user.\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\n\nI'm a little unsure on the code style for the `git add` section of\nthe error message. It works, but is tricky for a code reviewer\nto decipher the formatting. I don't think it should affect\ntranslation since the text is only a git command and folder names.\n\nChanges since v5:\n - Fixed memory leak reported by Ævar\n - Fixed error message formatting and addded TRANSLATOR tags\n - Removed \"resolution exists\"\n\n merge-ort.c                 | 61 +++++++++++++++++++++++++++++++++++++\n t/t6437-submodule-merge.sh  | 38 +++++++++++++++++++----\n t/t7402-submodule-rebase.sh |  9 ++++--\n 3 files changed, 100 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 01f150ef3b..147be0ce31 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -387,6 +387,9 @@ struct merge_options_internal {\n \n \t/* call_depth: recursion level counter for merging merge bases */\n \tint call_depth;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct string_list conflicted_submodules;\n };\n \n struct version_info {\n@@ -686,6 +689,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \n \tmem_pool_discard(&opti->pool, 0);\n \n+\tstring_list_clear(&opti->conflicted_submodules, 1);\n+\n \t/* Clean out callback_data as well. */\n \tFREE_AND_NULL(renames->callback_data);\n \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n@@ -1849,6 +1854,17 @@ static int merge_submodule(struct merge_options *opt,\n \n \tobject_array_clear(&merges);\n cleanup:\n+\tif (!ret) {\n+\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n+\t\tchar *util;\n+\t\tconst char *abbrev;\n+\n+\t\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n+\t\tutil = xstrdup(abbrev);\n+\n+\t\tstring_list_append(csub, path)->util = util;\n+\t}\n+\n \trepo_clear(&subrepo);\n \treturn ret;\n }\n@@ -4412,6 +4428,47 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+static void print_submodule_conflict_suggestion(struct string_list *csub) {\n+\tint first = 1;\n+\tif (csub->nr > 0) {\n+\t\tstruct string_list_item *item;\n+\t\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n+\t\t\t\"Please manually handle the merging of each conflicted submodule.\\n\"\n+\t\t\t\"This can be accomplished with the following steps:\\n\"));\n+\n+\t\tfor_each_string_list_item(item, csub) {\n+            const char *abbrev= item->util;\n+\t\t\t/*\n+\t\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n+\t\t\t * in a submodule. The second argument is the abbreviated id of the\n+\t\t\t * commit that needs to be merged.\n+\t\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n+\t\t\t */\n+\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n+\t\t\t\t    \"   or update to an existing commit which has merged those changes\\n\"),\n+\t\t\t\t\titem->string, abbrev);\n+\t\t}\n+\t\tprintf(_(\" - come back to superproject and run:\\n\\n\"));\n+\t\tfor_each_string_list_item(item, csub)\n+\t\t\t/*\n+\t\t\t * TRANSLATORS: This is a line of a recommended `git add` command\n+\t\t\t * with multiple lines of submodule folders.\n+\t\t\t * E.g.:     git add sub \\\n+\t\t\t *                   sub2 \\\n+\t\t\t *                   sub3\n+\t\t\t */\n+\t\t\tif (first) {\n+\t\t\t\tprintf(\"       git add %s\", item->string);\n+\t\t\t\tfirst = 0;\n+\t\t\t} else {\n+ \t\t\t\tprintf(\" \\\\\\n               %s\", item->string);\n+\t\t\t}\n+\t\tprintf(_(\"\\n\\n   to record the above merge or update\\n\"\n+\t\t\t\" - resolve any other conflicts in the superproject\\n\"\n+\t\t\t\" - commit the resulting index in the superproject\\n\"));\n+\t}\n+}\n+\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result)\n@@ -4461,6 +4518,9 @@ void merge_display_update_messages(struct merge_options *opt,\n \t}\n \tstring_list_clear(&olist, 0);\n \n+\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\tstring_list_clear(&opti->conflicted_submodules, 1);\n+\n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t       opti->renames.needed_limit, 0);\n@@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n \ttrace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n \tif (opt->priv) {\n \t\tclear_or_reinit_internal_opts(opt->priv, 1);\n+\t\tstring_list_init_dup(&opt->priv->conflicted_submodules);\n \t\ttrace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n \t\treturn;\n \t}\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex c253bf759a..84913525cf 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n \n+test_expect_success 'merging should conflict for non fast-forward' '\n+\ttest_when_finished \"git -C merge-search reset --hard\" &&\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi)\n+'\n+\n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse --short sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t ) &&\n \t if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t else\n \t\ttest_must_fail git merge c 2> actual\n \t fi &&\n@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n '\n \n \ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..ebeca12a71 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n+\t\tgrep \"$sub_expect\" actual_output\n+\tfi\n '\n \n test_done\n\nbase-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n-- \n2.37.1.359.gd136c6c3e2-goog\n\n"},{"id":"460006","messageId":"CABPp-BGvDLvmj720PFzsjrZrYuYauprL6JeOQhiQ4BjtfjF7Dg@mail.gmail.com","threadId":"57963","inReplyTo":"20220726210020.3397249-1-calvinwan@google.com","subject":"Re: [PATCH v6] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-27T01:13:17Z","receivedAt":"2022-07-27T01:13:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 26, 2022 at 2:00 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> When attempting to merge in a superproject with conflicting submodule\n> pointers that cannot be fast-forwarded or trivially resolved, the merge\n> fails and git prints an error message that accurately describes the\n\nmicro nit: s/git/Git/  (feel free to ignore since you're already at v6)\n\n> failure, but does not provide steps for the user to resolve the error.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. merge submodules or update submodules to an already existing\n>         commit that reflects the merge\n>  2. add submodules changes to the superproject\n>  3. finish merging superproject\n> These steps are non-obvious for newer submodule users to figure out\n> based on the error message and neither `git submodule status` nor `git\n> status` provide any useful pointers.\n>\n> Update error message to provide steps to resolve submodule merge\n> conflict. Future work could involve adding an advice flag to the\n> message. Although the message is long, it also has the id of the\n> commit that needs to be merged, which could be useful information\n> for the user.\n\nWell explained.  One very minor suggestion: perhaps change \"id of the\ncommit\" to \"id of the submodule commit\" just to make it slightly\nclearer that this information would take work for the user to discover\non their own?  (When I first read it, I was thinking, \"but they have\nthe commit, it's what they passed to merge\", before I realized my\nerror.)\n\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> ---\n>\n> I'm a little unsure on the code style for the `git add` section of\n> the error message. It works, but is tricky for a code reviewer\n> to decipher the formatting. I don't think it should affect\n> translation since the text is only a git command and folder names.\n>\n> Changes since v5:\n>  - Fixed memory leak reported by Ævar\n>  - Fixed error message formatting and addded TRANSLATOR tags\n>  - Removed \"resolution exists\"\n>\n>  merge-ort.c                 | 61 +++++++++++++++++++++++++++++++++++++\n>  t/t6437-submodule-merge.sh  | 38 +++++++++++++++++++----\n>  t/t7402-submodule-rebase.sh |  9 ++++--\n>  3 files changed, 100 insertions(+), 8 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 01f150ef3b..147be0ce31 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -387,6 +387,9 @@ struct merge_options_internal {\n>\n>         /* call_depth: recursion level counter for merging merge bases */\n>         int call_depth;\n> +\n> +       /* field that holds submodule conflict information */\n> +       struct string_list conflicted_submodules;\n>  };\n>\n>  struct version_info {\n> @@ -686,6 +689,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n>\n>         mem_pool_discard(&opti->pool, 0);\n>\n> +       string_list_clear(&opti->conflicted_submodules, 1);\n> +\n>         /* Clean out callback_data as well. */\n>         FREE_AND_NULL(renames->callback_data);\n>         renames->callback_data_nr = renames->callback_data_alloc = 0;\n> @@ -1849,6 +1854,17 @@ static int merge_submodule(struct merge_options *opt,\n\nSorry for not catching this in an earlier round, but merge_submodule()\nhas four \"return 0\" cases, for particular types of conflicts.  Those\nshould probably be switched to \"goto cleanup\" or something like that,\nso that these messages you are adding are also provided if one of\nthose conflict cases are hit.\n\n>         object_array_clear(&merges);\n>  cleanup:\n> +       if (!ret) {\n\nAnd here's another item I have to apologize for not catching in an\nearlier round.  We should also require !opt->priv->call_depth as well\nin this if-condition.  If merging of merge bases produced a submodule\nconflict, but the outer merge involves two sides that resolved the\ninner conflict the exact same way, then there's no conflict at the\nouter level and nothing for the user to resolve.  If users don't have\nany conflicts to resolve, we don't want to print messages telling them\nhow to resolve their non-existent conflicts.  And if there is still a\nconflict in the submodule for the outer merge as well as in the\nrecursive merge(s), we don't want to list the module twice (or more)\nwhen we tell the user to fix conflicts in their submodules (especially\nsince that means we'd be telling them to merge multiple different\ncommits for the single submodule, which could get confusing).\n\n> +               struct string_list *csub = &opt->priv->conflicted_submodules;\n> +               char *util;\n> +               const char *abbrev;\n> +\n> +               abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n> +               util = xstrdup(abbrev);\n> +\n> +               string_list_append(csub, path)->util = util;\n> +       }\n> +\n>         repo_clear(&subrepo);\n>         return ret;\n>  }\n> @@ -4412,6 +4428,47 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>         return errs;\n>  }\n>\n> +static void print_submodule_conflict_suggestion(struct string_list *csub) {\n> +       int first = 1;\n> +       if (csub->nr > 0) {\n> +               struct string_list_item *item;\n> +               printf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n> +                       \"Please manually handle the merging of each conflicted submodule.\\n\"\n> +                       \"This can be accomplished with the following steps:\\n\"));\n> +\n> +               for_each_string_list_item(item, csub) {\n> +            const char *abbrev= item->util;\n\nMessed up indent here?\n\n> +                       /*\n> +                        * TRANSLATORS: This is a line of advice to resolve a merge conflict\n> +                        * in a submodule. The second argument is the abbreviated id of the\n> +                        * commit that needs to be merged.\n> +                        * E.g. - go to submodule (sub), and either merge commit abc1234\"\n> +                        */\n> +                       printf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n> +                                   \"   or update to an existing commit which has merged those changes\\n\"),\n\nIndent may be wrong here too, at least if you're trying to get the\nleftmost quote marks to align.  (Maybe the non-alignment was\nintentional, it was just unclear because of the earlier strange\nindent.)\n\n> +                                       item->string, abbrev);\n> +               }\n> +               printf(_(\" - come back to superproject and run:\\n\\n\"));\n> +               for_each_string_list_item(item, csub)\n> +                       /*\n> +                        * TRANSLATORS: This is a line of a recommended `git add` command\n> +                        * with multiple lines of submodule folders.\n> +                        * E.g.:     git add sub \\\n> +                        *                   sub2 \\\n> +                        *                   sub3\n\nWhy does such a message need to be translated?  It's literal text the\nuser should type, right?  I'm not sure what a translator would do with\nthe message other than regurgitate it.\n\n> +                        */\n> +                       if (first) {\n> +                               printf(\"       git add %s\", item->string);\n\nBut if you did mean for there to be a translation and a TRANSLATORS\nnote, then did you forget to translate it by calling _()?\n\n> +                               first = 0;\n> +                       } else {\n> +                               printf(\" \\\\\\n               %s\", item->string);\n> +                       }\n\nCan we put braces around this for_each_string_list_item() block?  Or,\nas an alternative to the whole block, do you want to consider:\n\n   strub strbuf tmp = STRBUF_INIT;\n   strbuf_add_separated_string_list(&tmp, ' ', csub);\n   printf(_(\"    git add %s\"), tmp.buf);   /* or maybe remove the\ntranslation; not sure what the point is */\n   strbuf_release(&tmp);\n\n?  It is likely easier to copy & paste, and might be understood by\nmore users (I'm not sure how many are aware that command lines can use\nbackslashes for line continuation), but on the negative side, if you\nhave a lot of submodules it might make it harder to read.  Even if you\ndon't like space separated, though, you could still use this strategy\nby changing the second line to\n\n    strbuf_add_separated_string_list(&tmp, \" \\\\\\n               \", csub);\n\n> +               printf(_(\"\\n\\n   to record the above merge or update\\n\"\n> +                       \" - resolve any other conflicts in the superproject\\n\"\n> +                       \" - commit the resulting index in the superproject\\n\"));\n> +       }\n> +}\n> +\n>  void merge_display_update_messages(struct merge_options *opt,\n>                                    int detailed,\n>                                    struct merge_result *result)\n> @@ -4461,6 +4518,9 @@ void merge_display_update_messages(struct merge_options *opt,\n>         }\n>         string_list_clear(&olist, 0);\n>\n> +       print_submodule_conflict_suggestion(&opti->conflicted_submodules);\n> +       string_list_clear(&opti->conflicted_submodules, 1);\n> +\n\nIt would be more consistent to have things allocated in merge_start()\ncontinue to be cleared out in clear_or_reinit_internal_opts().  This\nkind of breaks that pairing, and you're already making sure to clear\nit there, so I'd rather remove this duplicate string_list_clear()\ncall.\n\n>         /* Also include needed rename limit adjustment now */\n>         diff_warn_rename_limit(\"merge.renamelimit\",\n>                                opti->renames.needed_limit, 0);\n> @@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n>         trace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n>         if (opt->priv) {\n>                 clear_or_reinit_internal_opts(opt->priv, 1);\n> +               string_list_init_dup(&opt->priv->conflicted_submodules);\n\nThis works, but there is a minor optimization available here if you're\ninterested (I understand if you're not since you're already at v6).\nAssuming you make the important opt->priv->call_depth fix, you can\nreplace string_list_init_dup() with string_list_init_nodup() here.\nThe paths aren't freed until clear_or_reinit_internal_opts() which\n(under the assumption previously stated) isn't called until\nmerge_finalize(), which comes after the call to\nmerge_display_update_messages(), which is where you use the data.\n\n(As repository paths are used all over merge-ort.c, the optimization\nto store them in one place (opt->priv->paths) and avoid duplicating\nthem is used pretty heavily.  It's more important for a lot of the\nother strmaps since they'll have a lot more paths in them, but it is\nkind of nice to use this optimization where possible.)\n\n>                 trace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n>                 return;\n>         }\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index c253bf759a..84913525cf 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n>          echo \"file-c\" > file-c &&\n>          git add file-c &&\n>          git commit -m \"sub-c\") &&\n> -       git commit -a -m \"c\" &&\n> +       git commit -a -m \"c\")\n> +'\n>\n> +test_expect_success 'merging should conflict for non fast-forward' '\n> +       test_when_finished \"git -C merge-search reset --hard\" &&\n> +       (cd merge-search &&\n> +        git checkout -b test-nonforward-a b &&\n> +         if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +         then\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n> +         else\n> +               test_must_fail git merge c 2> actual\n> +         fi)\n> +'\n> +\n> +test_expect_success 'finish setup for merge-search' '\n> +       (cd merge-search &&\n>         git checkout -b d a &&\n>         (cd sub &&\n>          git checkout -b sub-d sub-b &&\n> @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n>          test_cmp expect actual)\n>  '\n>\n> -test_expect_success 'merging should conflict for non fast-forward' '\n> +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n>         (cd merge-search &&\n> -        git checkout -b test-nonforward b &&\n> +        git checkout -b test-nonforward-b b &&\n>          (cd sub &&\n>           git rev-parse --short sub-d > ../expect) &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n> @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          ) &&\n>          if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>          then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>          else\n>                 test_must_fail git merge c 2> actual\n>          fi &&\n> @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>         git commit -a -m \"f\" &&\n>\n>         git checkout -b test-backward e &&\n> -       test_must_fail git merge f)\n> +       test_must_fail git merge f >actual &&\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +    then\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> +               grep \"$sub_expect\" actual\n> +       fi)\n>  '\n>\n>\n> diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\n> index 8e32f19007..ebeca12a71 100755\n> --- a/t/t7402-submodule-rebase.sh\n> +++ b/t/t7402-submodule-rebase.sh\n> @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n>         test_tick &&\n>         git commit -m fourth &&\n>\n> -       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n> +       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n>         git ls-files -s submodule >actual &&\n>         (\n>                 cd submodule &&\n> @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n>                 echo \"160000 $(git rev-parse HEAD^^) 2  submodule\" &&\n>                 echo \"160000 $(git rev-parse HEAD) 3    submodule\"\n>         ) >expect &&\n> -       test_cmp expect actual\n> +       test_cmp expect actual &&\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +    then\n> +               sub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n> +               grep \"$sub_expect\" actual_output\n> +       fi\n>  '\n>\n>  test_done\n>\n> base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n> --\n> 2.37.1.359.gd136c6c3e2-goog\n"},{"id":"460016","messageId":"220727.86ilnilxfv.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"20220726210020.3397249-1-calvinwan@google.com","subject":"Re: [PATCH v6] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-07-27T09:20:33Z","receivedAt":"2022-07-27T09:25:21Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jul 26 2022, Calvin Wan wrote:\n\n\nAside from what Elijah pointed out already...\n\n> +\n> +\t/* field that holds submodule conflict information */\n> +\tstruct string_list conflicted_submodules;\n\nLooks good!\n\n>  cleanup:\n> +\tif (!ret) {\n> +\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n> +\t\tchar *util;\n> +\t\tconst char *abbrev;\n> +\n> +\t\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n> +\t\tutil = xstrdup(abbrev);\n> +\n> +\t\tstring_list_append(csub, path)->util = util;\n\nElijah pointed out that these don't need to be dup'd at all, and you\nshould follow that advice. I'm not really familiar at all with this code\n(compared to him).\n\nFWIW the \"util\" here could be dropped in any case, in my version it was\nbecause we were making a struct, but the idiom for this would just be:\n\n\tstring_list_append(....)->util = xstrdup(...);\n\n> +\t\t\tprintf(_(\" - go to submodule (%s), and either merge commit %s\\n\"\n> +\t\t\t\t    \"   or update to an existing commit which has merged those changes\\n\"),\n> +\t\t\t\t\titem->string, abbrev);\n\nFWIW what I mentioned in v5 was to arrange this so that \" - \" or\nwhatever would be _()'d separately, so translators wouldn't need to\nworry about the formatting...\n\n> +\t\t\t\tprintf(\"       git add %s\", item->string);\n> +\t\t\t\tfirst = 0;\n> +\t\t\t} else {\n> + \t\t\t\tprintf(\" \\\\\\n               %s\", item->string);\n\nAnd if we're translating *some* whitespace we should translate all of\nit. In RTL languages the a string like \" foo\" needs to be translated as\n\"foo \". I.e. the whitespace from the \"right\" side of your terminal.\n\nThat was what I was pointing out in the object-name.c code,\nusage_with_options_internal() is another example.\n\n> +\t\t\t}\n> +\t\tprintf(_(\"\\n\\n   to record the above merge or update\\n\"\n\nWe can add \\n\\n unconditionally, no need to put it in the translation.\n\n"},{"id":"460039","messageId":"CAFySSZB6bB0qqCv5EPmJBJY9RbDRFv8JDYj89W+ND_Jw6Ys1kA@mail.gmail.com","threadId":"57963","inReplyTo":"CABPp-BGvDLvmj720PFzsjrZrYuYauprL6JeOQhiQ4BjtfjF7Dg@mail.gmail.com","subject":"Re: [PATCH v6] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-27T22:00:32Z","receivedAt":"2022-07-27T22:00:48Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> Well explained.  One very minor suggestion: perhaps change \"id of the\n> commit\" to \"id of the submodule commit\" just to make it slightly\n> clearer that this information would take work for the user to discover\n> on their own?  (When I first read it, I was thinking, \"but they have\n> the commit, it's what they passed to merge\", before I realized my\n> error.)\n\nack\n\n> Sorry for not catching this in an earlier round, but merge_submodule()\n> has four \"return 0\" cases, for particular types of conflicts.  Those\n> should probably be switched to \"goto cleanup\" or something like that,\n> so that these messages you are adding are also provided if one of\n> those conflict cases are hit.\n\nI didn't send these four \"return 0\" cases to cleanup because I thought\nthe error message wouldn't accurately reflect the resolution steps. Is\nmerging or updating the submodule still the correct resolution? The\nfirst three cases are for a null o/a/b, and the fourth case is for a missing\nlocal submodule. Also in cleanup, the subrepo is cleared but the\nsubrepo hasn't been initialized/failed to initialize in these four cases.\n\n> >         object_array_clear(&merges);\n> >  cleanup:\n> > +       if (!ret) {\n>\n> And here's another item I have to apologize for not catching in an\n> earlier round.  We should also require !opt->priv->call_depth as well\n> in this if-condition.  If merging of merge bases produced a submodule\n> conflict, but the outer merge involves two sides that resolved the\n> inner conflict the exact same way, then there's no conflict at the\n> outer level and nothing for the user to resolve.  If users don't have\n> any conflicts to resolve, we don't want to print messages telling them\n> how to resolve their non-existent conflicts.  And if there is still a\n> conflict in the submodule for the outer merge as well as in the\n> recursive merge(s), we don't want to list the module twice (or more)\n> when we tell the user to fix conflicts in their submodules (especially\n> since that means we'd be telling them to merge multiple different\n> commits for the single submodule, which could get confusing).\n\nack.\n\n> > +               for_each_string_list_item(item, csub) {\n> > +            const char *abbrev= item->util;\n>\n> Messed up indent here?\n\nLooks like going from my editor to `git format-patch` messed\nsomething up here.\n\n> > +               for_each_string_list_item(item, csub)\n> > +                       /*\n> > +                        * TRANSLATORS: This is a line of a recommended `git add` command\n> > +                        * with multiple lines of submodule folders.\n> > +                        * E.g.:     git add sub \\\n> > +                        *                   sub2 \\\n> > +                        *                   sub3\n>\n> Why does such a message need to be translated?  It's literal text the\n> user should type, right?  I'm not sure what a translator would do with\n> the message other than regurgitate it.\n\nIt doesn't. My point was to let the translator know that the only text\nin this print is for a git command. I should probably add that context\nto the comment though.\n\n> > +                        */\n> > +                       if (first) {\n> > +                               printf(\"       git add %s\", item->string);\n>\n> But if you did mean for there to be a translation and a TRANSLATORS\n> note, then did you forget to translate it by calling _()?\n\nSame reasoning as above.\n\n> > +                               first = 0;\n> > +                       } else {\n> > +                               printf(\" \\\\\\n               %s\", item->string);\n> > +                       }\n>\n> Can we put braces around this for_each_string_list_item() block?  Or,\n> as an alternative to the whole block, do you want to consider:\n>\n>    strub strbuf tmp = STRBUF_INIT;\n>    strbuf_add_separated_string_list(&tmp, ' ', csub);\n>    printf(_(\"    git add %s\"), tmp.buf);   /* or maybe remove the\n> translation; not sure what the point is */\n>    strbuf_release(&tmp);\n> ?  It is likely easier to copy & paste, and might be understood by\n> more users (I'm not sure how many are aware that command lines can use\n> backslashes for line continuation), but on the negative side, if you\n> have a lot of submodules it might make it harder to read.  Even if you\n> don't like space separated, though, you could still use this strategy\n> by changing the second line to\n>\n>     strbuf_add_separated_string_list(&tmp, \" \\\\\\n               \", csub);\n\nThis is a much cleaner implementation, thanks! If my goal is to make\nsubmodule merging easier for newer submodule users, then I think it's a\ngood assumption to remove any additional possible points of confusion,\naka with the \"command lines can use backslashes for line continuation\",\nso I'll swap over to spaces.\n\n> > +       print_submodule_conflict_suggestion(&opti->conflicted_submodules);\n> > +       string_list_clear(&opti->conflicted_submodules, 1);\n> > +\n>\n> It would be more consistent to have things allocated in merge_start()\n> continue to be cleared out in clear_or_reinit_internal_opts().  This\n> kind of breaks that pairing, and you're already making sure to clear\n> it there, so I'd rather remove this duplicate string_list_clear()\n> call.\n\nack.\n\n> >         /* Also include needed rename limit adjustment now */\n> >         diff_warn_rename_limit(\"merge.renamelimit\",\n> >                                opti->renames.needed_limit, 0);\n> > @@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n> >         trace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n> >         if (opt->priv) {\n> >                 clear_or_reinit_internal_opts(opt->priv, 1);\n> > +               string_list_init_dup(&opt->priv->conflicted_submodules);\n>\n> This works, but there is a minor optimization available here if you're\n> interested (I understand if you're not since you're already at v6).\n> Assuming you make the important opt->priv->call_depth fix, you can\n> replace string_list_init_dup() with string_list_init_nodup() here.\n> The paths aren't freed until clear_or_reinit_internal_opts() which\n> (under the assumption previously stated) isn't called until\n> merge_finalize(), which comes after the call to\n> merge_display_update_messages(), which is where you use the data.\n>\n> (As repository paths are used all over merge-ort.c, the optimization\n> to store them in one place (opt->priv->paths) and avoid duplicating\n> them is used pretty heavily.  It's more important for a lot of the\n> other strmaps since they'll have a lot more paths in them, but it is\n> kind of nice to use this optimization where possible.)\n\nThanks for all the context of this one. I agree it's minor, but any place\nwe can provide a better example to future contributors seems like a\nmore than worthy reason to make this change :)\n"},{"id":"460061","messageId":"CABPp-BF2O9mT4tX-1PuNmNvQwN8S_OmMz-Y8_yCu7tmVO_ZJmA@mail.gmail.com","threadId":"57963","inReplyTo":"CAFySSZB6bB0qqCv5EPmJBJY9RbDRFv8JDYj89W+ND_Jw6Ys1kA@mail.gmail.com","subject":"Re: [PATCH v6] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-28T00:41:34Z","receivedAt":"2022-07-28T00:41:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, Jul 27, 2022 at 3:00 PM Calvin Wan <calvinwan@google.com> wrote:\n[...]\n> > Sorry for not catching this in an earlier round, but merge_submodule()\n> > has four \"return 0\" cases, for particular types of conflicts.  Those\n> > should probably be switched to \"goto cleanup\" or something like that,\n> > so that these messages you are adding are also provided if one of\n> > those conflict cases are hit.\n>\n> I didn't send these four \"return 0\" cases to cleanup because I thought\n> the error message wouldn't accurately reflect the resolution steps. Is\n> merging or updating the submodule still the correct resolution? The\n> first three cases are for a null o/a/b, and the fourth case is for a missing\n> local submodule. Also in cleanup, the subrepo is cleared but the\n> subrepo hasn't been initialized/failed to initialize in these four cases.\n\nAh, I remember we partially discussed this earlier in this thread;\nsorry for forgetting.\n\nFor the failed to initialize case, yes we also need a merge -- the\nsubmodule is conflicted due to the lack of one.  The steps the user\nneeds to take are probably even more involved, though (they also need\nto initialize the submodule), so perhaps that one should be special\ncased.\n\nThe 'a' or 'b' being a null oid is actually dead code, as discussed\nearlier in the thread.  Perhaps we should change those two code paths\nfrom \"return 0\" to 'BUG(\"submodule deleted on one side; this should be\nhandled outside of merge_submodule()\")', and possibly with a commit\nmessage linking to\nhttps://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/\n(and mentioning the \"a and b being null oids within merge_submodule\nwill never trigger\" portion of that email).\n\nThe 'o' being a null oid is not dead code.  That particular case means\nthat there was no submodule in the merge base, but both sides of the\nmerge introduced the submodule and have it checked out to different\noids.  (At least, hopefully it's the same submodule.)  In that case,\nyes we do need some kind of merge.  So I think your message should\nprobably be included in this case, as-is.  Since the cleanup thing you\nmention is an issue, perhaps you need to refactor the code a bit so\nthat you can make this case somehow get the same message printed for\nusers?\n\nAll that said, if you want to defer any or all of this to a follow-on\nseries, that's fine...but it would be nice to have it mentioned in the\ncommit message.\n\n> > > +               for_each_string_list_item(item, csub)\n> > > +                       /*\n> > > +                        * TRANSLATORS: This is a line of a recommended `git add` command\n> > > +                        * with multiple lines of submodule folders.\n> > > +                        * E.g.:     git add sub \\\n> > > +                        *                   sub2 \\\n> > > +                        *                   sub3\n> >\n> > Why does such a message need to be translated?  It's literal text the\n> > user should type, right?  I'm not sure what a translator would do with\n> > the message other than regurgitate it.\n>\n> It doesn't. My point was to let the translator know that the only text\n> in this print is for a git command. I should probably add that context\n> to the comment though.\n\nUm...if the string doesn't need to be marked for translation, and\nisn't marked for translation, why would translators go looking for a\ncomment to help explain how to translate something that doesn't appear\nin their list of strings they need to translate?\n\nUsing\n    printf(\"    git add %s\", ...)\nmeans that the string \"    git add %s\" will not appear in the po/*.po\nfiles.  If it had been\n    printf(_(\"    git add %s\"), ...)\nthen it would appear in those files with filename(s) and line\nnumber(s) stating where the string had come from in case translators\nneeded to look for clues about the context in order to know how to\ntranslate the string.\n\nSo, I think you can just drop the comment.  Or am I still not\nunderstanding some nuance here?\n"},{"id":"460140","messageId":"CAFySSZAfvjHR9SJEwyZ4JB1=uHp+XAxnoxnvekQ=pZ6To46DXg@mail.gmail.com","threadId":"57963","inReplyTo":"CABPp-BF2O9mT4tX-1PuNmNvQwN8S_OmMz-Y8_yCu7tmVO_ZJmA@mail.gmail.com","subject":"Re: [PATCH v6] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-28T19:06:47Z","receivedAt":"2022-07-28T19:07:02Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> For the failed to initialize case, yes we also need a merge -- the\n> submodule is conflicted due to the lack of one.  The steps the user\n> needs to take are probably even more involved, though (they also need\n> to initialize the submodule), so perhaps that one should be special\n> cased.\n\nAdding a needswork bit to this one. Don't quite have the bandwidth to\nfigure out what would be a useful message in this case.\n\n> The 'a' or 'b' being a null oid is actually dead code, as discussed\n> earlier in the thread.  Perhaps we should change those two code paths\n> from \"return 0\" to 'BUG(\"submodule deleted on one side; this should be\n> handled outside of merge_submodule()\")', and possibly with a commit\n> message linking to\n> https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/\n> (and mentioning the \"a and b being null oids within merge_submodule\n> will never trigger\" portion of that email).\n>\n> The 'o' being a null oid is not dead code.  That particular case means\n> that there was no submodule in the merge base, but both sides of the\n> merge introduced the submodule and have it checked out to different\n> oids.  (At least, hopefully it's the same submodule.)  In that case,\n> yes we do need some kind of merge.  So I think your message should\n> probably be included in this case, as-is.  Since the cleanup thing you\n> mention is an issue, perhaps you need to refactor the code a bit so\n> that you can make this case somehow get the same message printed for\n> users?\n>\n> All that said, if you want to defer any or all of this to a follow-on\n> series, that's fine...but it would be nice to have it mentioned in the\n> commit message.\n\nBUGging out for 'a' and 'b' sounds reasonable to me. And I also do\nbelieve my error message applies to the 'o' case. I'll add a test to\nconfirm.\n\n> Um...if the string doesn't need to be marked for translation, and\n> isn't marked for translation, why would translators go looking for a\n> comment to help explain how to translate something that doesn't appear\n> in their list of strings they need to translate?\n\nAhhhhhhhhh I see...facepalm\n\n> Using\n>     printf(\"    git add %s\", ...)\n> means that the string \"    git add %s\" will not appear in the po/*.po\n> files.  If it had been\n>     printf(_(\"    git add %s\"), ...)\n> then it would appear in those files with filename(s) and line\n> number(s) stating where the string had come from in case translators\n> needed to look for clues about the context in order to know how to\n> translate the string.\n>\n> So, I think you can just drop the comment.  Or am I still not\n> understanding some nuance here?\n\nYou're not missing anything. Wasn't aware of how translators worked\nwith the project.\n"},{"id":"460152","messageId":"20220728211221.2913928-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220726210020.3397249-1-calvinwan@google.com","subject":"[PATCH v7] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-07-28T21:12:21Z","receivedAt":"2022-07-28T21:12:40Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded or trivially resolved, the merge\nfails and Git prints an error message that accurately describes the\nfailure, but does not provide steps for the user to resolve the error.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers.\n\nUpdate error message to provide steps to resolve submodule merge\nconflict. Future work could involve adding an advice flag to the\nmessage. Although the message is long, it also has the id of the \nsubmodule commit that needs to be merged, which could be useful\ninformation for the user.\n\nAdditionally, 4 merge failures that resulted in an early return have\nbeen updated to reflect the status of the merge.\n1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added\n   as a new conflict type and will print updated error message.\n2. Null merge side a (null a): BUG(). See [1] for discussion\n3. Null merge side b (null b): BUG(). See [1] for discussion\n4. Submodule not checked out: Still returns early, but added a\n   NEEDSWORK bit since current error message does not reflect the\n   correct resolution\n\n[1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\n\nFor Elijah: Cleaned up the small nits and updated resolutions for\nthose 4 cases we discussed.\n\nFor Ævar: Apologies for misunderstanding your suggestions to make\nmy messages easier for translators to work with. I have reformatted\nall of the messages to separate text vs formatting translations. Let\nme know if this is what you were expecting.\n\n merge-ort.c                 | 112 ++++++++++++++++++++++++++++++++++--\n t/t6437-submodule-merge.sh  |  78 +++++++++++++++++++++++--\n t/t7402-submodule-rebase.sh |   9 ++-\n 3 files changed, 185 insertions(+), 14 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 01f150ef3b..4302e900ee 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -387,6 +387,9 @@ struct merge_options_internal {\n \n \t/* call_depth: recursion level counter for merging merge bases */\n \tint call_depth;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct string_list conflicted_submodules;\n };\n \n struct version_info {\n@@ -517,6 +520,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n+\tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = {\n \t\t\"CONFLICT (submodule history not available)\",\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 no merge base)\"\n };\n \n struct logical_conflict_info {\n@@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \n \tmem_pool_discard(&opti->pool, 0);\n \n+\tstring_list_clear(&opti->conflicted_submodules, 1);\n+\n \t/* Clean out callback_data as well. */\n \tFREE_AND_NULL(renames->callback_data);\n \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n@@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt,\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n+\tint sub_initialized = 1;\n \n \t/* store fallback answer in result in case we fail */\n \toidcpy(result, opt->priv->call_depth ? o : a);\n \n \t/* we can not handle deletion conflicts */\n-\tif (is_null_oid(o))\n-\t\treturn 0;\n \tif (is_null_oid(a))\n-\t\treturn 0;\n+\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\"); \n \tif (is_null_oid(b))\n-\t\treturn 0;\n+\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n \n-\tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n+\tif ((sub_initialized = repo_submodule_init(&subrepo,\n+\t\t\t\t\t\t\t\t\topt->repo, path, null_oid()))) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n \t\t\t path);\n+\t\t/*\n+\t\t * NEEDSWORK: Since the steps to resolve this error are\n+\t\t * more involved than what is currently in \n+\t\t * print_submodule_conflict_suggestion(), we return\n+\t\t * immediately rather than generating an error message\n+\t\t */\n \t\treturn 0;\n \t}\n \n+\tif (is_null_oid(o)) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s (no merge base)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n+\t}\n+\n \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n \t    !(commit_a = lookup_commit_reference(&subrepo, a)) ||\n \t    !(commit_b = lookup_commit_reference(&subrepo, b))) {\n@@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt,\n \n \tobject_array_clear(&merges);\n cleanup:\n-\trepo_clear(&subrepo);\n+\tif (!opt->priv->call_depth && !ret) {\n+\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n+\n+\t\tstring_list_append(csub, path)->util =\n+\t\t\t\txstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n+\t}\n+\n+\tif (!sub_initialized)\n+\t\trepo_clear(&subrepo);\n \treturn ret;\n }\n \n@@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+static void print_submodule_conflict_suggestion(struct string_list *csub) {\n+\tif (csub->nr > 0) {\n+\t\tstruct string_list_item *item;\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, _(\"Please manually handle the merging of each conflicted submodule.\"));\n+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, _(\"This can be accomplished with the following steps:\"));\n+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tfor_each_string_list_item(item, csub) {\n+\t\t\tconst char *abbrev= item->util;\n+\t\t\t/*\n+\t\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n+\t\t\t * in a submodule. The second argument is the abbreviated id of the\n+\t\t\t * commit that needs to be merged.\n+\t\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n+\t\t\t */\n+\t\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\"),\n+\t\t\t\t\t\t\t\t\t\t\t\t\titem->string, abbrev);\n+\t\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n+\t\t\tstrbuf_addf(&msg, \"\\n\");\n+\t\t\tstrbuf_release(&tmp);\n+\t\t\tstrbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n+\t\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n+\t\t\tstrbuf_addf(&msg, \"\\n\");\n+\t\t\tstrbuf_release(&tmp);\n+\t\t}\n+\t\tstrbuf_addf(&tmp, _(\"come back to superproject and run:\"));\n+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n+\t\tstrbuf_addf(&msg, \"\\n\\n\");\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, \"git add \");\n+\t\tstrbuf_add_separated_string_list(&tmp, \" \", csub);\n+\t\tstrbuf_addf(&msg, _(\"       %s\"), tmp.buf);\n+\t\tstrbuf_addf(&msg, \"\\n\\n\");\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, _(\"to record the above merge or update\"));\n+\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n+\t\tstrbuf_addf(&msg, \"\\n\");\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, _(\"resolve any other conflicts in the superproject\"));\n+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n+\t\tstrbuf_addf(&msg, \"\\n\");\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tstrbuf_addf(&tmp, _(\"commit the resulting index in the superproject\"));\n+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n+\t\tstrbuf_addf(&msg, \"\\n\");\n+\t\tstrbuf_release(&tmp);\n+\n+\t\tprintf(\"%s\", msg.buf);\n+\t\tstrbuf_release(&msg);\n+\t}\n+}\n+\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result)\n@@ -4461,6 +4558,8 @@ void merge_display_update_messages(struct merge_options *opt,\n \t}\n \tstring_list_clear(&olist, 0);\n \n+\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t       opti->renames.needed_limit, 0);\n@@ -4657,6 +4756,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n \ttrace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n \tif (opt->priv) {\n \t\tclear_or_reinit_internal_opts(opt->priv, 1);\n+\t\tstring_list_init_nodup(&opt->priv->conflicted_submodules);\n \t\ttrace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n \t\treturn;\n \t}\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex c253bf759a..414597a420 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n \n+test_expect_success 'merging should conflict for non fast-forward' '\n+\ttest_when_finished \"git -C merge-search reset --hard\" &&\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi)\n+'\n+\n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse --short sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t ) &&\n \t if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t else\n \t\ttest_must_fail git merge c 2> actual\n \t fi &&\n@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n '\n \n \n@@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward\n \t)\n '\n \n+# Setup:\n+#   - Submodule has 2 commits: a and b\n+#   - Superproject branch 'a' adds and commits submodule pointing to 'commit a'\n+#   - Superproject branch 'b' adds and commits submodule pointing to 'commit b'\n+# If these two branches are now merged, there is no merge base\n+test_expect_success 'setup for null merge base' '\n+\tmkdir no-merge-base &&\n+\t(cd no-merge-base &&\n+\tgit init &&\n+\tmkdir sub &&\n+\t(cd sub &&\n+\t git init &&\n+\t echo \"file-a\" > file-a &&\n+\t git add file-a &&\n+\t git commit -m \"commit a\") &&\n+\tgit commit --allow-empty -m init &&\n+\tgit branch init &&\n+\tgit checkout -b a init &&\n+\tgit add sub &&\n+\tgit commit -m \"a\" &&\n+\tgit switch main &&\n+\t(cd sub &&\n+\t echo \"file-b\" > file-b &&\n+\t git add file-b &&\n+\t git commit -m \"commit b\"))\n+'\n+\n+test_expect_success 'merging should fail with no merge base' '\n+\t(cd no-merge-base &&\n+\tgit checkout -b b init &&\n+\tgit add sub &&\n+\tgit commit -m \"b\" &&\n+\ttest_must_fail git merge a >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n+'\n+\n test_done\ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..ebeca12a71 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n+\t\tgrep \"$sub_expect\" actual_output\n+\tfi\n '\n \n test_done\n\nbase-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n-- \n2.37.1.455.g008518b4e5-goog\n\n"},{"id":"460167","messageId":"220729.86y1wcixnz.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"20220728211221.2913928-1-calvinwan@google.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-07-28T23:22:57Z","receivedAt":"2022-07-29T00:13:12Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jul 28 2022, Calvin Wan wrote:\n\n> For Elijah: Cleaned up the small nits and updated resolutions for\n> those 4 cases we discussed.\n>\n> For Ævar: Apologies for misunderstanding your suggestions to make\n> my messages easier for translators to work with. I have reformatted\n> all of the messages to separate text vs formatting translations. Let\n> me know if this is what you were expecting.\n\nLet's take a look, and thanks for sticking with this...\n\n>\n>  merge-ort.c                 | 112 ++++++++++++++++++++++++++++++++++--\n>  t/t6437-submodule-merge.sh  |  78 +++++++++++++++++++++++--\n>  t/t7402-submodule-rebase.sh |   9 ++-\n>  3 files changed, 185 insertions(+), 14 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 01f150ef3b..4302e900ee 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -387,6 +387,9 @@ struct merge_options_internal {\n>  \n>  \t/* call_depth: recursion level counter for merging merge bases */\n>  \tint call_depth;\n> +\n> +\t/* field that holds submodule conflict information */\n> +\tstruct string_list conflicted_submodules;\n>  };\n>  \n>  struct version_info {\n> @@ -517,6 +520,7 @@ enum conflict_and_info_types {\n>  \tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n>  \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n>  \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n> +\tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n>  \n>  \t/* Keep this entry _last_ in the list */\n>  \tNB_CONFLICT_TYPES,\n> @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = {\n>  \t\t\"CONFLICT (submodule history not available)\",\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 no merge base)\"\n>  };\n>  \n>  struct logical_conflict_info {\n> @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n>  \n>  \tmem_pool_discard(&opti->pool, 0);\n>  \n> +\tstring_list_clear(&opti->conflicted_submodules, 1);\n> +\n>  \t/* Clean out callback_data as well. */\n>  \tFREE_AND_NULL(renames->callback_data);\n>  \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n> @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt,\n>  \n>  \tint i;\n>  \tint search = !opt->priv->call_depth;\n> +\tint sub_initialized = 1;\n>  \n>  \t/* store fallback answer in result in case we fail */\n>  \toidcpy(result, opt->priv->call_depth ? o : a);\n>  \n>  \t/* we can not handle deletion conflicts */\n> -\tif (is_null_oid(o))\n> -\t\treturn 0;\n>  \tif (is_null_oid(a))\n> -\t\treturn 0;\n> +\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\"); \n>  \tif (is_null_oid(b))\n> -\t\treturn 0;\n> +\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n>  \n> -\tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n> +\tif ((sub_initialized = repo_submodule_init(&subrepo,\n> +\t\t\t\t\t\t\t\t\topt->repo, path, null_oid()))) {\n>  \t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n>  \t\t\t path, NULL, NULL, NULL,\n>  \t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n>  \t\t\t path);\n> +\t\t/*\n> +\t\t * NEEDSWORK: Since the steps to resolve this error are\n> +\t\t * more involved than what is currently in \n> +\t\t * print_submodule_conflict_suggestion(), we return\n> +\t\t * immediately rather than generating an error message\n> +\t\t */\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (is_null_oid(o)) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s (no merge base)\"),\n> +\t\t\t path);\n> +\t\tgoto cleanup;\n> +\t}\n> +\n>  \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n>  \t    !(commit_a = lookup_commit_reference(&subrepo, a)) ||\n>  \t    !(commit_b = lookup_commit_reference(&subrepo, b))) {\n> @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt,\n>  \n>  \tobject_array_clear(&merges);\n>  cleanup:\n> -\trepo_clear(&subrepo);\n> +\tif (!opt->priv->call_depth && !ret) {\n> +\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n> +\n> +\t\tstring_list_append(csub, path)->util =\n> +\t\t\t\txstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n\nFWIW (since you may have changed this due to my comment) I meant (but\nmaybe didn't make clear enough) in\nhttps://lore.kernel.org/git/220727.86ilnilxfv.gmgdl@evledraar.gmail.com/\nthat just getting rid of the \"util\" variable could trivially be done, so:\n\n\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n\tconst char *abbrev;\n\n\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n\n\tstring_list_append(csub, path)->util = xstrdup(abbrev);\n\nWhat you have here is also just fine, but in case you traded the line\nvariable for line wrapping I think it's just fine (and actually\npreferable) to just make an intermediate variable to avoid this sort of\nline wrapping.\n\nAnyway, just clarifying...\n\n> +static void print_submodule_conflict_suggestion(struct string_list *csub) {\n> +\tif (csub->nr > 0) {\n\nSince the entire body of the function is guarded by this maybe avoid the\nindentation and:\n\n\tif (!csub->nr)\n\t\treturn;\n\n\n> +\t\tstruct string_list_item *item;\n> +\t\tstruct strbuf msg = STRBUF_INIT;\n> +\t\tstruct strbuf tmp = STRBUF_INIT;\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"Please manually handle the merging of each conflicted submodule.\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"This can be accomplished with the following steps:\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n\nThis was *almost* correct in your v6, i.e.:\n\n\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n\t\t \"Please manually handle the merging of each conflicted submodule.\\n\"\n\t\t \"This can be accomplished with the following steps:\\n\"));\n\nWhat I meant with \"We can add \\n\\n unconditionally, no need to put it in\nthe translation.\" in\nhttps://lore.kernel.org/git/220727.86ilnilxfv.gmgdl@evledraar.gmail.com/\nis that you should avoid adding formatting to translations themselves if\npossible.\n\ni.e. what we're translating here is a full paragraph, so to a translator\nit doesn't matter if it ends with \":\\n\" or \":\", so we can add the last\n\"\\n\" unconditionalyl.\n\nBut we should *not* add the \\n's within a single paragraph\nunconditionally, a translator needs to be able to translate that entire\nstring.\n\nDifferent languages split sentences differently, and different\nconventions in different languages & SVO v.s. OVS or wahtever (see\nhttps://en.wikipedia.org/wiki/Word_order) means that your last sentence\nmight need to come first in some languages.\n\nSo I think this should just be:\n\n\tprintf(_(\"Recursive merging with submodules currently only supports trivial cases.\\n\"\n\t\t \"Please manually handle the merging of each conflicted submodule.\\n\"\n\t\t \"This can be accomplished with the following steps:\"));\n\tputchar('\\n');\n\nI.e. the \"\\n\" within the pargraph are imporant, and translators need to\nbe able to amend those, and perhaps some languages will have 2x, some 4x\nor whatever.\n\nWhereas the \"\\n\" at the end is something we can always add, because it's\njust a matter of how we're interpolating the paragraph into other output\n(think *nix terminal v.s. say HTML, just as an example).\n\n> +\n> +\t\tfor_each_string_list_item(item, csub) {\n> +\t\t\tconst char *abbrev= item->util;\n> +\t\t\t/*\n> +\t\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n> +\t\t\t * in a submodule. The second argument is the abbreviated id of the\n> +\t\t\t * commit that needs to be merged.\n> +\t\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n> +\t\t\t */\n> +\t\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\"),\n> +\t\t\t\t\t\t\t\t\t\t\t\t\titem->string, abbrev);\n\n...\n\n> +\t\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +\t\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\t\tstrbuf_release(&tmp);\n> +\t\t\tstrbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n> +\t\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n> +\t\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\t\tstrbuf_release(&tmp);\n\nSimilarly this is worse, because we're now splitting up up a single\nsentence or paragraph into multiple translations.\n\nFWIW I meant that you could write some helper function that would do\nsomething like this (in Perl, too lazy to write C now):\n\n\tperl -wE '\n\t\tmy @lines = split /\\n/, $ARGV[0];\n\t\tfor (my $i = 0; $i < @lines; $i++) {\n\t\t\tmy $pfx = !$i ? \"- \" : \"  \"; say \"$pfx$lines[$i]\";\n\t}' \"$(printf \"some multi\\nline paragraph\\nhere\")\"\n\t- some multi\n\t  line paragraph\n\t  here\n\nI.e. the translator would see:\n\n\t_(\"some multi\\nline paragraph\\nhere\")\n\nWhich would allow them to insert any amount of newlines, but you'd just\nhave a utility function that:\n\n * Splits those lines by \\n\n * Formats the first line by _(\"- %s\\n\")\n * Formats subsequent lines with _(\"  %s\\n\") \n * The %s in those is a _()'d string.\n\nI.e. what's happening at the bottom of show_ambiguous_object(). It's:\n\n * A relatively small amount of programming,\n * Lets translators focus only on these formatting questions for the\n   translations that do the magic formatting, and those only need to be\n   changed for RTL languages.\n\n   I.e. German and English whill both want \"- %s\\n\", but e.g. Hebrew\n   will want \"%s -\\n\".\n\n * Makes the code easier to read, because instead of adding formatting concerns to every string, you'd just:\n\n\tstrbuf_addstr(&buf, add_fmt_list_item(i, _(\"blah bla\\nblah blah\\nblah.)));\n\n> +\t\tstrbuf_release(&tmp);\n\nWhen you use the strbuf API you should strbuf_reset() within a single\nscope if you want to \"clear\" it, not strbuf_release().\n\nThe strbuf_release() also works, but then it's a malloc()/free(), each\ntime, with strbuf_reset() we don't free() it, we just set the len to\nzero, and the content to \"\\0\", but remember how long it was (\"alloc\" in\nstrbuf.h).\n\nYou then only do the strbuf_release() at the very end.\n"},{"id":"460168","messageId":"CABPp-BE9hdzn_8fqSa-JYXS14xbXDEvKi=yupvWDFAUnALhD9A@mail.gmail.com","threadId":"57963","inReplyTo":"20220728211221.2913928-1-calvinwan@google.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-07-29T00:24:35Z","receivedAt":"2022-07-29T00:24:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 28, 2022 at 2:12 PM Calvin Wan <calvinwan@google.com> wrote:\n>\n> When attempting to merge in a superproject with conflicting submodule\n> pointers that cannot be fast-forwarded or trivially resolved, the merge\n> fails and Git prints an error message that accurately describes the\n> failure, but does not provide steps for the user to resolve the error.\n>\n> Git is left in a conflicted state, which requires the user to:\n>  1. merge submodules or update submodules to an already existing\n>         commit that reflects the merge\n>  2. add submodules changes to the superproject\n>  3. finish merging superproject\n> These steps are non-obvious for newer submodule users to figure out\n> based on the error message and neither `git submodule status` nor `git\n> status` provide any useful pointers.\n>\n> Update error message to provide steps to resolve submodule merge\n> conflict. Future work could involve adding an advice flag to the\n> message. Although the message is long, it also has the id of the\n> submodule commit that needs to be merged, which could be useful\n> information for the user.\n>\n> Additionally, 4 merge failures that resulted in an early return have\n> been updated to reflect the status of the merge.\n> 1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added\n>    as a new conflict type and will print updated error message.\n> 2. Null merge side a (null a): BUG(). See [1] for discussion\n> 3. Null merge side b (null b): BUG(). See [1] for discussion\n> 4. Submodule not checked out: Still returns early, but added a\n>    NEEDSWORK bit since current error message does not reflect the\n>    correct resolution\n\ns/does not reflect the correct resolution/also deserves a more\ndetailed explanation of how to resolve/ ?\n\n>\n> [1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/\n>\n> Signed-off-by: Calvin Wan <calvinwan@google.com>\n> ---\n>\n> For Elijah: Cleaned up the small nits and updated resolutions for\n> those 4 cases we discussed.\n\nThanks.  Including a range-diff in the cover letter would be really\nhelpful, for future reference.\n\n>\n> For Ævar: Apologies for misunderstanding your suggestions to make\n> my messages easier for translators to work with. I have reformatted\n> all of the messages to separate text vs formatting translations. Let\n> me know if this is what you were expecting.\n>\n>  merge-ort.c                 | 112 ++++++++++++++++++++++++++++++++++--\n>  t/t6437-submodule-merge.sh  |  78 +++++++++++++++++++++++--\n>  t/t7402-submodule-rebase.sh |   9 ++-\n>  3 files changed, 185 insertions(+), 14 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 01f150ef3b..4302e900ee 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -387,6 +387,9 @@ struct merge_options_internal {\n>\n>         /* call_depth: recursion level counter for merging merge bases */\n>         int call_depth;\n> +\n> +       /* field that holds submodule conflict information */\n> +       struct string_list conflicted_submodules;\n>  };\n>\n>  struct version_info {\n> @@ -517,6 +520,7 @@ enum conflict_and_info_types {\n>         CONFLICT_SUBMODULE_NOT_INITIALIZED,\n>         CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n>         CONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n> +       CONFLICT_SUBMODULE_NULL_MERGE_BASE,\n>\n>         /* Keep this entry _last_ in the list */\n>         NB_CONFLICT_TYPES,\n> @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = {\n>                 \"CONFLICT (submodule history not available)\",\n>         [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n>                 \"CONFLICT (submodule may have rewinds)\",\n> +       [CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n> +               \"CONFLICT (submodule no merge base)\"\n>  };\n>\n>  struct logical_conflict_info {\n> @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n>\n>         mem_pool_discard(&opti->pool, 0);\n>\n> +       string_list_clear(&opti->conflicted_submodules, 1);\n> +\n>         /* Clean out callback_data as well. */\n>         FREE_AND_NULL(renames->callback_data);\n>         renames->callback_data_nr = renames->callback_data_alloc = 0;\n> @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt,\n>\n>         int i;\n>         int search = !opt->priv->call_depth;\n> +       int sub_initialized = 1;\n>\n>         /* store fallback answer in result in case we fail */\n>         oidcpy(result, opt->priv->call_depth ? o : a);\n>\n>         /* we can not handle deletion conflicts */\n> -       if (is_null_oid(o))\n> -               return 0;\n>         if (is_null_oid(a))\n> -               return 0;\n> +               BUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n>         if (is_null_oid(b))\n> -               return 0;\n> +               BUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n>\n> -       if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n> +       if ((sub_initialized = repo_submodule_init(&subrepo,\n> +                                                                       opt->repo, path, null_oid()))) {\n\nUm, repo_submodule_init() returns 0 on success, non-zero upon failure.\nSo !sub_initialized means \"submodule IS initialized\", though it\nappears to read to mean the opposite.  Can you consider renaming your\nvariable (maybe just to \"sub_not_initialized\")?\n\n>                 path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n>                          path, NULL, NULL, NULL,\n>                          _(\"Failed to merge submodule %s (not checked out)\"),\n>                          path);\n> +               /*\n> +                * NEEDSWORK: Since the steps to resolve this error are\n> +                * more involved than what is currently in\n> +                * print_submodule_conflict_suggestion(), we return\n> +                * immediately rather than generating an error message\n> +                */\n\nTechnically, we just did generate an error message (\"Failed to merge\nsubmodule %s (not checked out)\").\n\nMaybe replace \"immediately rather than generating an error message\"\nwith \"immediately.  It would be better to \"goto cleanup\" here, after\nsetting some flag requesting a more detailed message be saved for\nprint_submodule_conflict_suggetion()\".  Or something like that.\n\n>                 return 0;\n>         }\n>\n> +       if (is_null_oid(o)) {\n> +               path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n> +                        path, NULL, NULL, NULL,\n> +                        _(\"Failed to merge submodule %s (no merge base)\"),\n> +                        path);\n> +               goto cleanup;\n> +       }\n\nDoes this need to be moved after initializing the submodule?  I\nthought that was the point of introducing the sub_initialized\nvariable, and we're clearly not going to use it, so it would seem to\nmake more sense to not initialize it for this case.\n\n> +\n>         if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n>             !(commit_a = lookup_commit_reference(&subrepo, a)) ||\n>             !(commit_b = lookup_commit_reference(&subrepo, b))) {\n> @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt,\n>\n>         object_array_clear(&merges);\n>  cleanup:\n> -       repo_clear(&subrepo);\n> +       if (!opt->priv->call_depth && !ret) {\n> +               struct string_list *csub = &opt->priv->conflicted_submodules;\n> +\n> +               string_list_append(csub, path)->util =\n> +                               xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n> +       }\n> +\n> +       if (!sub_initialized)\n> +               repo_clear(&subrepo);\n>         return ret;\n>  }\n>\n> @@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>         return errs;\n>  }\n>\n> +static void print_submodule_conflict_suggestion(struct string_list *csub) {\n> +       if (csub->nr > 0) {\n> +               struct string_list_item *item;\n> +               struct strbuf msg = STRBUF_INIT;\n> +               struct strbuf tmp = STRBUF_INIT;\n> +\n> +               strbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n> +               strbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +               strbuf_release(&tmp);\n\nInstead of these three lines, why not just\n    strbuf_addstr(&msg, \"%s\\n\", _(\"Recursive merging with submodules\ncurrently only supports trivial cases.\"));\n?\n\nSame applies for some of the messages below too.\n\n> +               strbuf_addf(&tmp, _(\"Please manually handle the merging of each conflicted submodule.\"));\n> +               strbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +               strbuf_release(&tmp);\n> +\n> +               strbuf_addf(&tmp, _(\"This can be accomplished with the following steps:\"));\n> +               strbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +               strbuf_release(&tmp);\n> +\n> +               for_each_string_list_item(item, csub) {\n> +                       const char *abbrev= item->util;\n> +                       /*\n> +                        * TRANSLATORS: This is a line of advice to resolve a merge conflict\n> +                        * in a submodule. The second argument is the abbreviated id of the\n> +                        * commit that needs to be merged.\n> +                        * E.g. - go to submodule (sub), and either merge commit abc1234\"\n> +                        */\n> +                       strbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\"),\n> +                                                                                                       item->string, abbrev);\n> +                       strbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +                       strbuf_addf(&msg, \"\\n\");\n> +                       strbuf_release(&tmp);\n> +                       strbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n> +                       strbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n> +                       strbuf_addf(&msg, \"\\n\");\n> +                       strbuf_release(&tmp);\n> +               }\n> +               strbuf_addf(&tmp, _(\"come back to superproject and run:\"));\n> +               strbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +               strbuf_addf(&msg, \"\\n\\n\");\n> +               strbuf_release(&tmp);\n> +\n> +               strbuf_addf(&tmp, \"git add \");\n> +               strbuf_add_separated_string_list(&tmp, \" \", csub);\n> +               strbuf_addf(&msg, _(\"       %s\"), tmp.buf);\n> +               strbuf_addf(&msg, \"\\n\\n\");\n> +               strbuf_release(&tmp);\n> +\n> +               strbuf_addf(&tmp, _(\"to record the above merge or update\"));\n> +               strbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n> +               strbuf_addf(&msg, \"\\n\");\n> +               strbuf_release(&tmp);\n> +\n> +               strbuf_addf(&tmp, _(\"resolve any other conflicts in the superproject\"));\n> +               strbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +               strbuf_addf(&msg, \"\\n\");\n> +               strbuf_release(&tmp);\n> +\n> +               strbuf_addf(&tmp, _(\"commit the resulting index in the superproject\"));\n> +               strbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +               strbuf_addf(&msg, \"\\n\");\n> +               strbuf_release(&tmp);\n> +\n> +               printf(\"%s\", msg.buf);\n> +               strbuf_release(&msg);\n\nYuck.  I'm not a translator, so maybe what you are doing is preferred.\nBut wouldn't translators find it annoying to have to translate \" - %s\"\nand \"  %s\" in all these places (and wouldn't there need to be a\nTRANSLATORS comment before each and every one)?  Also, are we running\nthe risk of these short strings needing to be translated differently\nhere than in other places?  For example, what if someone is trying to\nalign output nicely:\n\n    Name:   John Johnson\n    Date:   2022-07-28\n    Gender: Male\n\nand uses _(\"  %s\") when printing the name to get the indent right, and\nexpects other languages to add or remove spaces accordingly?  Or has\nother built up messages without hyphens but uses _(\"  %s\") for\nwhatever reason that may not match what you are doing here?\n\nIf there are multiple _(\"  %s\") in the code, that string can only be\ntranslated once.  Not once per codepath, but once total.  I'm afraid\nthis lego building of messages seems to risk needing\ntranslation-per-codepath when that just isn't possible.\n\nI also find the big block of code somewhat painful to read.  Could we\ninstead do something like (note, I have both a tmp and tmp2):\n\n    strbuf_add_separated_string_list(&tmp2, \" \", csub);\n\n    for_each_string_list_item(item, csub) {\n        const char *abbrev= item->util;\n        /*\n         * TRANSLATORS: This is a line of advice to resolve a merge conflict\n         * in a submodule. The second argument is the abbreviated id of the\n         * commit that needs to be merged.\n         * E.g. - go to submodule (sub), and either merge commit abc1234\n         */\n        strbuf_addf(&tmp, _(\" - go to submodule %s, and either merge\ncommit %s\\n\"\n                            \"   or update to an existing commit which\nhas merged those changes\\n\"),\n                            item->string, abbrev);\n    }\n\n    strbuf_addf(&msg,\n                _(\"Recursive merging with submodules currently only\nsupports trivial cases.\\n\"\n                  \"Please manually handle the merging of each\nconflicted submodule.\\n\"\n                  \"This can be accomplished with the following steps:\\n\"\n                  \"%s\"\n                  \" - come back to superproject and run:\\n\\n\"\n                  \"      git add %s\\n\\n\"\n                  \"   to record the above merge or update\\n\"\n                  \" - resolve any other conflicts in the superproject\\n\"\n                  \" - commit the resulting index in the superproject\\n\"),\n                  tmp.buf, tmp2.buf);\n\nThis will give translators precisely two messages to translate (and we\ncan't drop it to one since one of the two is repeated a variable\nnumber of times), and provide more built-in context about how to\ntranslate since the whole message is involved.  If one of the messages\ntranslates into something especially long, they can even add line\nbreaks and reflow the paragraph in ways that make sense for them,\nwhich your current version just doesn't permit.\n\n\n> +       }\n> +}\n> +\n>  void merge_display_update_messages(struct merge_options *opt,\n>                                    int detailed,\n>                                    struct merge_result *result)\n> @@ -4461,6 +4558,8 @@ void merge_display_update_messages(struct merge_options *opt,\n>         }\n>         string_list_clear(&olist, 0);\n>\n> +       print_submodule_conflict_suggestion(&opti->conflicted_submodules);\n> +\n>         /* Also include needed rename limit adjustment now */\n>         diff_warn_rename_limit(\"merge.renamelimit\",\n>                                opti->renames.needed_limit, 0);\n> @@ -4657,6 +4756,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n>         trace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n>         if (opt->priv) {\n>                 clear_or_reinit_internal_opts(opt->priv, 1);\n> +               string_list_init_nodup(&opt->priv->conflicted_submodules);\n>                 trace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n>                 return;\n>         }\n> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\n> index c253bf759a..414597a420 100755\n> --- a/t/t6437-submodule-merge.sh\n> +++ b/t/t6437-submodule-merge.sh\n> @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n>          echo \"file-c\" > file-c &&\n>          git add file-c &&\n>          git commit -m \"sub-c\") &&\n> -       git commit -a -m \"c\" &&\n> +       git commit -a -m \"c\")\n> +'\n>\n> +test_expect_success 'merging should conflict for non fast-forward' '\n> +       test_when_finished \"git -C merge-search reset --hard\" &&\n> +       (cd merge-search &&\n> +        git checkout -b test-nonforward-a b &&\n> +         if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +         then\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n> +         else\n> +               test_must_fail git merge c 2> actual\n> +         fi)\n> +'\n> +\n> +test_expect_success 'finish setup for merge-search' '\n> +       (cd merge-search &&\n>         git checkout -b d a &&\n>         (cd sub &&\n>          git checkout -b sub-d sub-b &&\n> @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n>          test_cmp expect actual)\n>  '\n>\n> -test_expect_success 'merging should conflict for non fast-forward' '\n> +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n>         (cd merge-search &&\n> -        git checkout -b test-nonforward b &&\n> +        git checkout -b test-nonforward-b b &&\n>          (cd sub &&\n>           git rev-parse --short sub-d > ../expect) &&\n>           if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>           then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>           else\n>                 test_must_fail git merge c 2> actual\n>           fi &&\n> @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n>          ) &&\n>          if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>          then\n> -               test_must_fail git merge c >actual\n> +               test_must_fail git merge c >actual &&\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n> +               grep \"$sub_expect\" actual\n>          else\n>                 test_must_fail git merge c 2> actual\n>          fi &&\n> @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n>         git commit -a -m \"f\" &&\n>\n>         git checkout -b test-backward e &&\n> -       test_must_fail git merge f)\n> +       test_must_fail git merge f >actual &&\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +    then\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n> +               grep \"$sub_expect\" actual\n> +       fi)\n>  '\n>\n>\n> @@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward\n>         )\n>  '\n>\n> +# Setup:\n> +#   - Submodule has 2 commits: a and b\n> +#   - Superproject branch 'a' adds and commits submodule pointing to 'commit a'\n> +#   - Superproject branch 'b' adds and commits submodule pointing to 'commit b'\n> +# If these two branches are now merged, there is no merge base\n> +test_expect_success 'setup for null merge base' '\n> +       mkdir no-merge-base &&\n> +       (cd no-merge-base &&\n> +       git init &&\n> +       mkdir sub &&\n> +       (cd sub &&\n> +        git init &&\n> +        echo \"file-a\" > file-a &&\n> +        git add file-a &&\n> +        git commit -m \"commit a\") &&\n> +       git commit --allow-empty -m init &&\n> +       git branch init &&\n> +       git checkout -b a init &&\n> +       git add sub &&\n> +       git commit -m \"a\" &&\n> +       git switch main &&\n> +       (cd sub &&\n> +        echo \"file-b\" > file-b &&\n> +        git add file-b &&\n> +        git commit -m \"commit b\"))\n> +'\n> +\n> +test_expect_success 'merging should fail with no merge base' '\n> +       (cd no-merge-base &&\n> +       git checkout -b b init &&\n> +       git add sub &&\n> +       git commit -m \"b\" &&\n> +       test_must_fail git merge a >actual &&\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +    then\n> +               sub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)\" &&\n> +               grep \"$sub_expect\" actual\n> +       fi)\n> +'\n> +\n>  test_done\n> diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\n> index 8e32f19007..ebeca12a71 100755\n> --- a/t/t7402-submodule-rebase.sh\n> +++ b/t/t7402-submodule-rebase.sh\n> @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n>         test_tick &&\n>         git commit -m fourth &&\n>\n> -       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n> +       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n>         git ls-files -s submodule >actual &&\n>         (\n>                 cd submodule &&\n> @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n>                 echo \"160000 $(git rev-parse HEAD^^) 2  submodule\" &&\n>                 echo \"160000 $(git rev-parse HEAD) 3    submodule\"\n>         ) >expect &&\n> -       test_cmp expect actual\n> +       test_cmp expect actual &&\n> +       if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> +    then\n> +               sub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n> +               grep \"$sub_expect\" actual_output\n> +       fi\n>  '\n>\n>  test_done\n>\n> base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n> --\n> 2.37.1.455.g008518b4e5-goog\n>\n"},{"id":"460345","messageId":"220801.864jywi2th.gmgdl@evledraar.gmail.com","threadId":"57963","inReplyTo":"20220728211221.2913928-1-calvinwan@google.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-01T12:06:42Z","receivedAt":"2022-08-01T12:29:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jul 28 2022, Calvin Wan wrote:\n\n> +\t\tstrbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n\nAside from anything else mentioned, please run the \"static-analysis\" job\nin CI (or coccicheck locally) to sanity check patches. The \"seen\" branch\nis currently failing on this & similar API use:\nhttps://github.com/git/git/runs/7587382291?check_suite_focus=true\n\nPer my previous feedback we should change this anyway, but that would\nhave shown that doing this particular thing is better done with\nstrbuf_addbuf() (and actually buggy with strbuf_addf(), if we had a\nvector where format specifiers could be inserted).\n"},{"id":"460392","messageId":"CAFySSZBdTvxn8QjOujvu2orkf_tA4Y1nufdjaNJHLsUTWsoo-A@mail.gmail.com","threadId":"57963","inReplyTo":"CABPp-BE9hdzn_8fqSa-JYXS14xbXDEvKi=yupvWDFAUnALhD9A@mail.gmail.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-08-01T22:24:33Z","receivedAt":"2022-08-01T22:24:51Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> > 4. Submodule not checked out: Still returns early, but added a\n> >    NEEDSWORK bit since current error message does not reflect the\n> >    correct resolution\n>\n> s/does not reflect the correct resolution/also deserves a more\n> detailed explanation of how to resolve/ ?\n\nack\n\n> Thanks.  Including a range-diff in the cover letter would be really\n> helpful, for future reference.\n\nwill do\n\n> Um, repo_submodule_init() returns 0 on success, non-zero upon failure.\n> So !sub_initialized means \"submodule IS initialized\", though it\n> appears to read to mean the opposite.  Can you consider renaming your\n> variable (maybe just to \"sub_not_initialized\")?\n\nack\n\n\n> Technically, we just did generate an error message (\"Failed to merge\n> submodule %s (not checked out)\").\n>\n> Maybe replace \"immediately rather than generating an error message\"\n> with \"immediately.  It would be better to \"goto cleanup\" here, after\n> setting some flag requesting a more detailed message be saved for\n> print_submodule_conflict_suggetion()\".  Or something like that.\n\nSounds like a better place to put the NEEDSWORK bit.\n\n>\n> >                 return 0;\n> >         }\n> >\n> > +       if (is_null_oid(o)) {\n> > +               path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n> > +                        path, NULL, NULL, NULL,\n> > +                        _(\"Failed to merge submodule %s (no merge base)\"),\n> > +                        path);\n> > +               goto cleanup;\n> > +       }\n>\n> Does this need to be moved after initializing the submodule?  I\n> thought that was the point of introducing the sub_initialized\n> variable, and we're clearly not going to use it, so it would seem to\n> make more sense to not initialize it for this case.\n\nThe submodule needs to be initialized to generate part of the error\nmessage. See the following in cleanup:\n\nrepo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)\n\n> Yuck.  I'm not a translator, so maybe what you are doing is preferred.\n> But wouldn't translators find it annoying to have to translate \" - %s\"\n> and \"  %s\" in all these places (and wouldn't there need to be a\n> TRANSLATORS comment before each and every one)?\n\nAs per Avar's suggestion, I made a helper function so translators\nonly have to translate \" - %s\" and \"  %s\" once. This change does\nend up lining up the `git add ...` command, but I think that's fine\n\n- come back to superproject and run:\n\n   git add sub3 sub2 sub\n\n   to record the above merge or update\n\n> I also find the big block of code somewhat painful to read.  Could we\n> instead do something like (note, I have both a tmp and tmp2):\n>\n>     strbuf_add_separated_string_list(&tmp2, \" \", csub);\n>\n>     for_each_string_list_item(item, csub) {\n>         const char *abbrev= item->util;\n>         /*\n>          * TRANSLATORS: This is a line of advice to resolve a merge conflict\n>          * in a submodule. The second argument is the abbreviated id of the\n>          * commit that needs to be merged.\n>          * E.g. - go to submodule (sub), and either merge commit abc1234\n>          */\n>         strbuf_addf(&tmp, _(\" - go to submodule %s, and either merge\n> commit %s\\n\"\n>                             \"   or update to an existing commit which\n> has merged those changes\\n\"),\n>                             item->string, abbrev);\n>     }\n>\n>     strbuf_addf(&msg,\n>                 _(\"Recursive merging with submodules currently only\n> supports trivial cases.\\n\"\n>                   \"Please manually handle the merging of each\n> conflicted submodule.\\n\"\n>                   \"This can be accomplished with the following steps:\\n\"\n>                   \"%s\"\n>                   \" - come back to superproject and run:\\n\\n\"\n>                   \"      git add %s\\n\\n\"\n>                   \"   to record the above merge or update\\n\"\n>                   \" - resolve any other conflicts in the superproject\\n\"\n>                   \" - commit the resulting index in the superproject\\n\"),\n>                   tmp.buf, tmp2.buf);\n>\n> This will give translators precisely two messages to translate (and we\n> can't drop it to one since one of the two is repeated a variable\n> number of times), and provide more built-in context about how to\n> translate since the whole message is involved.  If one of the messages\n> translates into something especially long, they can even add line\n> breaks and reflow the paragraph in ways that make sense for them,\n> which your current version just doesn't permit.\n\nI think Avar's suggestion makes translating the formatting easier than\nyour suggestion, at the cost of having a big block of code to setup it\nall up.\n"},{"id":"460399","messageId":"xmqqpmhjjwo9.fsf@gitster.g","threadId":"57963","inReplyTo":"20220728211221.2913928-1-calvinwan@google.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-02T00:50:30Z","receivedAt":"2022-08-02T00:50:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n>  merge-ort.c                 | 112 ++++++++++++++++++++++++++++++++++--\n>  t/t6437-submodule-merge.sh  |  78 +++++++++++++++++++++++--\n>  t/t7402-submodule-rebase.sh |   9 ++-\n>  3 files changed, 185 insertions(+), 14 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 01f150ef3b..4302e900ee 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -387,6 +387,9 @@ struct merge_options_internal {\n>  \n>  \t/* call_depth: recursion level counter for merging merge bases */\n>  \tint call_depth;\n> +\n> +\t/* field that holds submodule conflict information */\n> +\tstruct string_list conflicted_submodules;\n>  };\n>  \n>  struct version_info {\n> @@ -517,6 +520,7 @@ enum conflict_and_info_types {\n>  \tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n>  \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n>  \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n> +\tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n>  \n>  \t/* Keep this entry _last_ in the list */\n>  \tNB_CONFLICT_TYPES,\n> @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = {\n>  \t\t\"CONFLICT (submodule history not available)\",\n>  \t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n>  \t\t\"CONFLICT (submodule may have rewinds)\",\n\nThe other ones are semi sentences ...\n\n> +\t[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n> +\t\t\"CONFLICT (submodule no merge base)\"\n\n... and this wants to become one, too, e.g. \"submodule lacks merge\nbase\", perhaps?\n\n>  };\n\n> @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n>  \n>  \tmem_pool_discard(&opti->pool, 0);\n>  \n> +\tstring_list_clear(&opti->conflicted_submodules, 1);\n> +\n>  \t/* Clean out callback_data as well. */\n>  \tFREE_AND_NULL(renames->callback_data);\n>  \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n> @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt,\n>  \n>  \tint i;\n>  \tint search = !opt->priv->call_depth;\n> +\tint sub_initialized = 1;\n>  \n>  \t/* store fallback answer in result in case we fail */\n>  \toidcpy(result, opt->priv->call_depth ? o : a);\n>  \n>  \t/* we can not handle deletion conflicts */\n> -\tif (is_null_oid(o))\n> -\t\treturn 0;\n>  \tif (is_null_oid(a))\n> -\t\treturn 0;\n> +\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\"); \n>  \tif (is_null_oid(b))\n> -\t\treturn 0;\n> +\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n\nOK.  It is interesting that the first condition (i.e. 'o' is\nmissing) we are removing is not about \"we cannot handle deletion\",\nso this change corrects the code to match the comment.  As the BUG()\nmessages are the same on these sides,\n\n\tif (is_null_oid(a) || is_null_oid(b))\n\t\tBUG(...);\n\nmay be easier to read, perhaps?\n\n> -\tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n> +\tif ((sub_initialized = repo_submodule_init(&subrepo,\n> +\t\t\t\t\t\t\t\t\topt->repo, path, null_oid()))) {\n\nI wonder where this overly deep indentation come from?  Can the\n.editorconfig file we ship with the project help?\n\n>  \t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n>  \t\t\t path, NULL, NULL, NULL,\n>  \t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n>  \t\t\t path);\n> +\t\t/*\n> +\t\t * NEEDSWORK: Since the steps to resolve this error are\n> +\t\t * more involved than what is currently in \n> +\t\t * print_submodule_conflict_suggestion(), we return\n> +\t\t * immediately rather than generating an error message\n> +\t\t */\n\nOK.\n\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (is_null_oid(o)) {\n> +\t\tpath_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n> +\t\t\t path, NULL, NULL, NULL,\n> +\t\t\t _(\"Failed to merge submodule %s (no merge base)\"),\n> +\t\t\t path);\n\nOK.\n\n> +\t\tgoto cleanup;\n> +\t}\n\n> @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt,\n>  \n>  \tobject_array_clear(&merges);\n>  cleanup:\n> -\trepo_clear(&subrepo);\n> +\tif (!opt->priv->call_depth && !ret) {\n> +\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n> +\n> +\t\tstring_list_append(csub, path)->util =\n> +\t\t\t\txstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n\nThis line could also lose one level of indent.\n\nWe record 'b' in its abbreviated form here, because the assumption\nis that when merging the superproject, 'a' (which is the assumed\nfallback answer wet up at the beginning of the furnction) is the\ncommit recorded in our side of the superproject, and it is the\ncommit checked out in the submodule, so the task of making the\nnecessary merge in the submodule is to go to the submodule and then\nmerge 'b' into HEAD.  Makes sense.\n\n> @@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>  \treturn errs;\n>  }\n>  \n> +static void print_submodule_conflict_suggestion(struct string_list *csub) {\n> +\tif (csub->nr > 0) {\n> +\t\tstruct string_list_item *item;\n> +\t\tstruct strbuf msg = STRBUF_INIT;\n> +\t\tstruct strbuf tmp = STRBUF_INIT;\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"Please manually handle the merging of each conflicted submodule.\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"This can be accomplished with the following steps:\"));\n> +\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tfor_each_string_list_item(item, csub) {\n> +\t\t\tconst char *abbrev= item->util;\n> +\t\t\t/*\n> +\t\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n> +\t\t\t * in a submodule. The second argument is the abbreviated id of the\n> +\t\t\t * commit that needs to be merged.\n> +\t\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n> +\t\t\t */\n> +\t\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\"),\n> +\t\t\t\t\t\t\t\t\t\t\t\t\titem->string, abbrev);\n\nIs this also an instance of overly deep indentation (it is so deep\nthat I cannot easily tell)?\n\nWhen we say \"either merge commit %s\" (where %s is 'b'---what they\nhave as we saw earlier), do we need to mention what the value of 'a'\nis to the user, or is it redundant because we are absolutely sure\nthat 'a' is what is checked out in the submodule?\n\n> +\t\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +\t\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\t\tstrbuf_release(&tmp);\n> +\t\t\tstrbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n\n\"those changes\" means \"a..b\"?  Again, I just want to make sure that\nthe user in this situation knows \"the other end\" of the range when\nthey are told only about 'b'.\n\n> +\t\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n> +\t\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\t\tstrbuf_release(&tmp);\n> +\t\t}\n> +\t\tstrbuf_addf(&tmp, _(\"come back to superproject and run:\"));\n> +\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +\t\tstrbuf_addf(&msg, \"\\n\\n\");\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, \"git add \");\n> +\t\tstrbuf_add_separated_string_list(&tmp, \" \", csub);\n> +\t\tstrbuf_addf(&msg, _(\"       %s\"), tmp.buf);\n> +\t\tstrbuf_addf(&msg, \"\\n\\n\");\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"to record the above merge or update\"));\n> +\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n> +\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"resolve any other conflicts in the superproject\"));\n> +\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tstrbuf_addf(&tmp, _(\"commit the resulting index in the superproject\"));\n> +\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n> +\t\tstrbuf_addf(&msg, \"\\n\");\n> +\t\tstrbuf_release(&tmp);\n> +\n> +\t\tprintf(\"%s\", msg.buf);\n> +\t\tstrbuf_release(&msg);\n> +\t}\n> +}\n\nOK.  Makes sense.\n\nThanks.\n"},{"id":"460504","messageId":"CAFySSZCEWX22h1FBX65=eofNm+WU97DtL7P+11WFie72DQLDkg@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqpmhjjwo9.fsf@gitster.g","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-08-02T21:03:07Z","receivedAt":"2022-08-02T21:03:21Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> > @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = {\n> >               \"CONFLICT (submodule history not available)\",\n> >       [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n> >               \"CONFLICT (submodule may have rewinds)\",\n>\n> The other ones are semi sentences ...\n>\n> > +     [CONFLICT_SUBMODULE_NULL_MERGE_BASE] =\n> > +             \"CONFLICT (submodule no merge base)\"\n>\n> ... and this wants to become one, too, e.g. \"submodule lacks merge\n> base\", perhaps?\n\nlacks/missing both SGTM\n\n\n>         if (is_null_oid(a) || is_null_oid(b))\n>                 BUG(...);\n>\n> may be easier to read, perhaps?\n\nack\n\n> I wonder where this overly deep indentation come from?  Can the\n> .editorconfig file we ship with the project help?\n\nIt comes from me not properly indenting the line. I'll take a look at\nthe .editorconfig file.\n\n> When we say \"either merge commit %s\" (where %s is 'b'---what they\n> have as we saw earlier), do we need to mention what the value of 'a'\n> is to the user, or is it redundant because we are absolutely sure\n> that 'a' is what is checked out in the submodule?\n\nWe are absolutely sure that 'a' is the GITLINK to the submodule, but\nif the user changes their local submodule without updating the reference,\nit can result in errors like \"not checked out\" and \"commits not present\"\nduring the merge.\n\n> > +                     strbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n>\n> \"those changes\" means \"a..b\"?  Again, I just want to make sure that\n> the user in this situation knows \"the other end\" of the range when\n> they are told only about 'b'.\n\nCorrect. Based on what I said above, it might be worthwhile to mention\n'a' as well since that information could also help the user in those cases.\nI'm thinking something like this:\n\n\"go to submodule ('sub' : 'a'), and either merge commit 'b'\\n\"\n\"go to submodule ('sub', 'a'), and either merge commit 'b'\\n\"\n\"go to submodule 'sub', commit 'a', and either merge commit 'b'\\n\"\n"},{"id":"460505","messageId":"xmqqo7x2fj05.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZCEWX22h1FBX65=eofNm+WU97DtL7P+11WFie72DQLDkg@mail.gmail.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-02T21:11:38Z","receivedAt":"2022-08-02T21:11:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> I'm thinking something like this:\n>\n> \"go to submodule ('sub' : 'a'), and either merge commit 'b'\\n\"\n> \"go to submodule ('sub', 'a'), and either merge commit 'b'\\n\"\n> \"go to submodule 'sub', commit 'a', and either merge commit 'b'\\n\"\n\nIn the first two, I suspect that it may not be quite clear what 'a'\nmeans to the user.  In the third one, the first \"commit\" might be\nmistaken as a verb.  I am tempted to say\n\n    cd to <sub>, run \"checkout --detach <a>\" then \"merge <b>\"\n\nbut that may be a bit too prescriptive.  I dunno.\n\n\n\n"},{"id":"460512","messageId":"CAFySSZA2FzUfN94Gr6a_470GzjeT7V1zqHrdBe6YUQ0Tv3pdvg@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqo7x2fj05.fsf@gitster.g","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-08-02T21:55:51Z","receivedAt":"2022-08-02T21:56:11Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"I'm starting to think this is getting out of scope for my patch.\nFor the errors, \"not checked out\" and \"commits not present\",\nI will have a NEEDSWORK bit attached to them in\nprint_submodule_conflict(), and if any of the submodules has\nthose errors, then my message won't print out. That way,\nwe are guaranteed to have 'a' checked out when my message\nprints, rendering it redundant.\n"},{"id":"460514","messageId":"xmqq4jyuffqe.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZA2FzUfN94Gr6a_470GzjeT7V1zqHrdBe6YUQ0Tv3pdvg@mail.gmail.com","subject":"Re: [PATCH v7] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-02T22:22:17Z","receivedAt":"2022-08-02T22:22:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> I'm starting to think this is getting out of scope for my patch.\n> For the errors, \"not checked out\" and \"commits not present\",\n> I will have a NEEDSWORK bit attached to them in\n> print_submodule_conflict(), and if any of the submodules has\n> those errors, then my message won't print out. That way,\n> we are guaranteed to have 'a' checked out when my message\n> prints, rendering it redundant.\n\nThat's fine, then.\n\nThanks for thinking it through.\n\n"},{"id":"460657","messageId":"20220804195105.1303455-1-calvinwan@google.com","threadId":"57963","inReplyTo":"20220728211221.2913928-1-calvinwan@google.com","subject":"[PATCH v8] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-08-04T19:51:05Z","receivedAt":"2022-08-04T19:51:36Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"When attempting to merge in a superproject with conflicting submodule\npointers that cannot be fast-forwarded or trivially resolved, the merge\nfails and Git prints an error message that accurately describes the\nfailure, but does not provide steps for the user to resolve the error.\n\nGit is left in a conflicted state, which requires the user to:\n 1. merge submodules or update submodules to an already existing\n\tcommit that reflects the merge\n 2. add submodules changes to the superproject\n 3. finish merging superproject\nThese steps are non-obvious for newer submodule users to figure out\nbased on the error message and neither `git submodule status` nor `git\nstatus` provide any useful pointers.\n\nUpdate error message to provide steps to resolve submodule merge\nconflict. Future work could involve adding an advice flag to the\nmessage. Although the message is long, it also has the id of the \nsubmodule commit that needs to be merged, which could be useful\ninformation for the user.\n\nAdditionally, 5 merge failures that resulted in an early return have\nbeen updated to reflect the status of the merge.\n1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added\n   as a new conflict type and will print updated error message.\n2. Null merge side a (null a): BUG(). See [1] for discussion\n3. Null merge side b (null b): BUG(). See [1] for discussion\n4. Submodule not checked out: added NEEDSWORK bit\n5. Submodule commits not present: added NEEDSWORK bit\nThe errors with a NEEDSWORK bit deserve a more detailed explanation of\nhow to resolve them. See [2] for more context.\n\n[1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/\n[2] https://lore.kernel.org/git/xmqqpmhjjwo9.fsf@gitster.g/\n\nSigned-off-by: Calvin Wan <calvinwan@google.com>\n---\nRange-diff against v7:\n1:  0640133250 ! 1:  24a4652b25 submodule merge: update conflict error message\n    @@ merge-ort.c: struct merge_options_internal {\n     +\n     +\t/* field that holds submodule conflict information */\n     +\tstruct string_list conflicted_submodules;\n    ++};\n    ++\n    ++struct conflicted_submodule_item {\n    ++\tchar *abbrev;\n    ++\tint flag;\n      };\n      \n    ++static void conflicted_submodule_item_free(void *util, const char *str)\n    ++{\n    ++\tstruct conflicted_submodule_item *item = util;\n    ++\n    ++\tfree(item->abbrev);\n    ++\tfree(item);\n    ++}\n    ++\n      struct version_info {\n    + \tstruct object_id oid;\n    + \tunsigned short mode;\n     @@ merge-ort.c: enum conflict_and_info_types {\n      \tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n      \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n    @@ merge-ort.c: 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 no merge base)\"\n    ++\t\t\"CONFLICT (submodule lacks merge base)\"\n      };\n      \n      struct logical_conflict_info {\n    @@ merge-ort.c: static void clear_or_reinit_internal_opts(struct merge_options_inte\n      \n      \tmem_pool_discard(&opti->pool, 0);\n      \n    -+\tstring_list_clear(&opti->conflicted_submodules, 1);\n    ++\tstring_list_clear_func(&opti->conflicted_submodules,\n    ++\t\t\t\t\tconflicted_submodule_item_free);\n     +\n      \t/* Clean out callback_data as well. */\n      \tFREE_AND_NULL(renames->callback_data);\n    @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      \n      \tint i;\n      \tint search = !opt->priv->call_depth;\n    -+\tint sub_initialized = 1;\n    ++\tint sub_not_initialized = 1;\n    ++\tint sub_flag = -1;\n      \n      \t/* store fallback answer in result in case we fail */\n      \toidcpy(result, opt->priv->call_depth ? o : a);\n    @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      \t/* we can not handle deletion conflicts */\n     -\tif (is_null_oid(o))\n     -\t\treturn 0;\n    - \tif (is_null_oid(a))\n    +-\tif (is_null_oid(a))\n     -\t\treturn 0;\n    -+\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\"); \n    - \tif (is_null_oid(b))\n    +-\tif (is_null_oid(b))\n     -\t\treturn 0;\n    ++\tif (is_null_oid(a) || is_null_oid(b))\n     +\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n      \n     -\tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n    -+\tif ((sub_initialized = repo_submodule_init(&subrepo,\n    -+\t\t\t\t\t\t\t\t\topt->repo, path, null_oid()))) {\n    ++\tif ((sub_not_initialized = repo_submodule_init(&subrepo,\n    ++\t\topt->repo, path, null_oid()))) {\n      \t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n      \t\t\t path, NULL, NULL, NULL,\n      \t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n      \t\t\t path);\n    -+\t\t/*\n    -+\t\t * NEEDSWORK: Since the steps to resolve this error are\n    -+\t\t * more involved than what is currently in \n    -+\t\t * print_submodule_conflict_suggestion(), we return\n    -+\t\t * immediately rather than generating an error message\n    -+\t\t */\n    - \t\treturn 0;\n    - \t}\n    - \n    +-\t\treturn 0;\n    ++\t\tsub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED;\n    ++\t\tgoto cleanup;\n    ++\t}\n    ++\n     +\tif (is_null_oid(o)) {\n     +\t\tpath_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n     +\t\t\t path, NULL, NULL, NULL,\n     +\t\t\t _(\"Failed to merge submodule %s (no merge base)\"),\n     +\t\t\t path);\n     +\t\tgoto cleanup;\n    -+\t}\n    -+\n    + \t}\n    + \n      \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n    - \t    !(commit_a = lookup_commit_reference(&subrepo, a)) ||\n    - \t    !(commit_b = lookup_commit_reference(&subrepo, b))) {\n    +@@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n    + \t\t\t path, NULL, NULL, NULL,\n    + \t\t\t _(\"Failed to merge submodule %s (commits not present)\"),\n    + \t\t\t path);\n    ++\t\tsub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE;\n    + \t\tgoto cleanup;\n    + \t}\n    + \n     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      \n      \tobject_array_clear(&merges);\n    @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n     -\trepo_clear(&subrepo);\n     +\tif (!opt->priv->call_depth && !ret) {\n     +\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n    ++\t\tstruct conflicted_submodule_item *util;\n    ++\t\tconst char *abbrev;\n     +\n    -+\t\tstring_list_append(csub, path)->util =\n    -+\t\t\t\txstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV));\n    ++\t\tutil = xmalloc(sizeof(*util));\n    ++\t\tutil->flag = sub_flag;\n    ++\t\tutil->abbrev = NULL;\n    ++\t\tif (!sub_not_initialized) {\n    ++\t\t\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n    ++\t\t\tutil->abbrev = xstrdup(abbrev);\n    ++\t\t}\n    ++\t\tstring_list_append(csub, path)->util = util;\n     +\t}\n     +\n    -+\tif (!sub_initialized)\n    ++\tif (!sub_not_initialized)\n     +\t\trepo_clear(&subrepo);\n      \treturn ret;\n      }\n    @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op\n      \treturn errs;\n      }\n      \n    -+static void print_submodule_conflict_suggestion(struct string_list *csub) {\n    -+\tif (csub->nr > 0) {\n    -+\t\tstruct string_list_item *item;\n    -+\t\tstruct strbuf msg = STRBUF_INIT;\n    -+\t\tstruct strbuf tmp = STRBUF_INIT;\n    -+\n    -+\t\tstrbuf_addf(&tmp, _(\"Recursive merging with submodules currently only supports trivial cases.\"));\n    -+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n    -+\t\tstrbuf_release(&tmp);\n    ++static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n    ++\tstruct strbuf tmp = STRBUF_INIT;\n    ++\tstruct string_list msg_list = STRING_LIST_INIT_DUP;\n    ++\tint i;\n     +\n    -+\t\tstrbuf_addf(&tmp, _(\"Please manually handle the merging of each conflicted submodule.\"));\n    -+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n    -+\t\tstrbuf_release(&tmp);\n    -+\n    -+\t\tstrbuf_addf(&tmp, _(\"This can be accomplished with the following steps:\"));\n    -+\t\tstrbuf_addf(&msg, \"%s\\n\", tmp.buf);\n    -+\t\tstrbuf_release(&tmp);\n    -+\n    -+\t\tfor_each_string_list_item(item, csub) {\n    -+\t\t\tconst char *abbrev= item->util;\n    ++\tstring_list_split(&msg_list, msg->buf, '\\n', -1);\n    ++\tfor (i = 0; i < msg_list.nr; i++) {\n    ++\t\tif (!i)\n     +\t\t\t/*\n    -+\t\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n    -+\t\t\t * in a submodule. The second argument is the abbreviated id of the\n    -+\t\t\t * commit that needs to be merged.\n    -+\t\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n    ++\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n    ++\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n    ++\t\t\t * languages, the following swap is suggested:\n    ++\t\t\t *      \" - %s\\n\" -> \"%s - \\n\"\n     +\t\t\t */\n    -+\t\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\"),\n    -+\t\t\t\t\t\t\t\t\t\t\t\t\titem->string, abbrev);\n    -+\t\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n    -+\t\t\tstrbuf_addf(&msg, \"\\n\");\n    -+\t\t\tstrbuf_release(&tmp);\n    -+\t\t\tstrbuf_addf(&tmp, _(\"or update to an existing commit which has merged those changes\"));\n    -+\t\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n    -+\t\t\tstrbuf_addf(&msg, \"\\n\");\n    -+\t\t\tstrbuf_release(&tmp);\n    -+\t\t}\n    -+\t\tstrbuf_addf(&tmp, _(\"come back to superproject and run:\"));\n    -+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n    -+\t\tstrbuf_addf(&msg, \"\\n\\n\");\n    -+\t\tstrbuf_release(&tmp);\n    ++\t\t\tstrbuf_addf(&tmp, _(\" - %s\\n\"), msg_list.items[i].string);\n    ++\t\telse\n    ++\t\t\t/*\n    ++\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n    ++\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n    ++\t\t\t * languages, the following swap is suggested:\n    ++\t\t\t *      \"   %s\\n\" -> \"%s   \\n\"\n    ++\t\t\t */\n    ++\t\t\tstrbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n    ++\t}\n    ++\tstrbuf_reset(msg);\n    ++\tstrbuf_add(msg, tmp.buf, tmp.len);\n    ++}\n     +\n    -+\t\tstrbuf_addf(&tmp, \"git add \");\n    -+\t\tstrbuf_add_separated_string_list(&tmp, \" \", csub);\n    -+\t\tstrbuf_addf(&msg, _(\"       %s\"), tmp.buf);\n    -+\t\tstrbuf_addf(&msg, \"\\n\\n\");\n    -+\t\tstrbuf_release(&tmp);\n    ++static void print_submodule_conflict_suggestion(struct string_list *csub) {\n    ++\tstruct string_list_item *item;\n    ++\tstruct strbuf msg = STRBUF_INIT;\n    ++\tstruct strbuf tmp = STRBUF_INIT;\n    ++\tstruct strbuf subs = STRBUF_INIT;\n     +\n    -+\t\tstrbuf_addf(&tmp, _(\"to record the above merge or update\"));\n    -+\t\tstrbuf_addf(&msg, _(\"   %s\"), tmp.buf);\n    -+\t\tstrbuf_addf(&msg, \"\\n\");\n    -+\t\tstrbuf_release(&tmp);\n    ++\tif (!csub->nr)\n    ++\t\treturn;\n    ++\t\n    ++\t/*\n    ++\t * NEEDSWORK: The steps to resolve these errors deserve a more\n    ++\t * detailed explanation than what is currently printed below.\n    ++\t */\n    ++\tfor_each_string_list_item(item, csub) {\n    ++\t\tstruct conflicted_submodule_item *util = item->util;\n     +\n    -+\t\tstrbuf_addf(&tmp, _(\"resolve any other conflicts in the superproject\"));\n    -+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n    -+\t\tstrbuf_addf(&msg, \"\\n\");\n    -+\t\tstrbuf_release(&tmp);\n    ++\t\tif (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED ||\n    ++\t\t\tutil->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE)\n    ++\t\t\treturn;\n    ++\t}\n     +\n    -+\t\tstrbuf_addf(&tmp, _(\"commit the resulting index in the superproject\"));\n    -+\t\tstrbuf_addf(&msg, _(\" - %s\"), tmp.buf);\n    -+\t\tstrbuf_addf(&msg, \"\\n\");\n    -+\t\tstrbuf_release(&tmp);\n    ++\tprintf(_(\"Recursive merging with submodules currently only supports \"\n    ++\t\t\"trivial cases.\\nPlease manually handle the merging of each \"\n    ++\t\t\"conflicted submodule.\\nThis can be accomplished with the following \"\n    ++\t\t\"steps:\"));\n    ++\tputchar('\\n');\n     +\n    -+\t\tprintf(\"%s\", msg.buf);\n    -+\t\tstrbuf_release(&msg);\n    ++\tfor_each_string_list_item(item, csub) {\n    ++\t\tstruct conflicted_submodule_item *util = item->util;\n    ++\t\t/*\n    ++\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n    ++\t\t * in a submodule. The second argument is the abbreviated id of the\n    ++\t\t * commit that needs to be merged.\n    ++\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n    ++\t\t */\n    ++\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\\n\"\n    ++\t\t\t\"or update to an existing commit which has merged those changes\"),\n    ++\t\t\titem->string, util->abbrev);\n    ++\t\tformat_submodule_conflict_suggestion(&tmp);\n    ++\t\tstrbuf_add(&msg, tmp.buf, tmp.len);\n    ++\t\tstrbuf_reset(&tmp);\n     +\t}\n    ++\tstrbuf_add_separated_string_list(&subs, \" \", csub);\n    ++\tstrbuf_addstr(&tmp, _(\"come back to superproject and run:\"));\n    ++\tstrbuf_addf(&tmp, \"\\n\\ngit add %s\\n\\n\", subs.buf);\n    ++\tstrbuf_addstr(&tmp, _(\"to record the above merge or update\"));\n    ++\tformat_submodule_conflict_suggestion(&tmp);\n    ++\tstrbuf_add(&msg, tmp.buf, tmp.len);\n    ++\tstrbuf_reset(&tmp);\n    ++\n    ++\tstrbuf_addstr(&tmp, _(\"resolve any other conflicts in the superproject\"));\n    ++\tformat_submodule_conflict_suggestion(&tmp);\n    ++\tstrbuf_add(&msg, tmp.buf, tmp.len);\n    ++\tstrbuf_reset(&tmp);\n    ++\n    ++\tstrbuf_addstr(&tmp, _(\"commit the resulting index in the superproject\"));\n    ++\tformat_submodule_conflict_suggestion(&tmp);\n    ++\tstrbuf_add(&msg, tmp.buf, tmp.len);\n    ++\n    ++\tprintf(\"%s\", msg.buf);\n    ++\tstrbuf_release(&subs);\n    ++\tstrbuf_release(&tmp);\n    ++\tstrbuf_release(&msg);\n     +}\n     +\n      void merge_display_update_messages(struct merge_options *opt,\n\n merge-ort.c                 | 161 ++++++++++++++++++++++++++++++++++--\n t/t6437-submodule-merge.sh  |  78 +++++++++++++++--\n t/t7402-submodule-rebase.sh |   9 +-\n 3 files changed, 231 insertions(+), 17 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 01f150ef3b..86985d5c61 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -387,8 +387,24 @@ struct merge_options_internal {\n \n \t/* call_depth: recursion level counter for merging merge bases */\n \tint call_depth;\n+\n+\t/* field that holds submodule conflict information */\n+\tstruct string_list conflicted_submodules;\n+};\n+\n+struct conflicted_submodule_item {\n+\tchar *abbrev;\n+\tint flag;\n };\n \n+static void conflicted_submodule_item_free(void *util, const char *str)\n+{\n+\tstruct conflicted_submodule_item *item = util;\n+\n+\tfree(item->abbrev);\n+\tfree(item);\n+}\n+\n struct version_info {\n \tstruct object_id oid;\n \tunsigned short mode;\n@@ -517,6 +533,7 @@ enum conflict_and_info_types {\n \tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n \tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n \tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n+\tCONFLICT_SUBMODULE_NULL_MERGE_BASE,\n \n \t/* Keep this entry _last_ in the list */\n \tNB_CONFLICT_TYPES,\n@@ -570,6 +587,8 @@ static const char *type_short_descriptions[] = {\n \t\t\"CONFLICT (submodule history not available)\",\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 };\n \n struct logical_conflict_info {\n@@ -686,6 +705,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \n \tmem_pool_discard(&opti->pool, 0);\n \n+\tstring_list_clear_func(&opti->conflicted_submodules,\n+\t\t\t\t\tconflicted_submodule_item_free);\n+\n \t/* Clean out callback_data as well. */\n \tFREE_AND_NULL(renames->callback_data);\n \trenames->callback_data_nr = renames->callback_data_alloc = 0;\n@@ -1744,24 +1766,32 @@ static int merge_submodule(struct merge_options *opt,\n \n \tint i;\n \tint search = !opt->priv->call_depth;\n+\tint sub_not_initialized = 1;\n+\tint sub_flag = -1;\n \n \t/* store fallback answer in result in case we fail */\n \toidcpy(result, opt->priv->call_depth ? o : a);\n \n \t/* we can not handle deletion conflicts */\n-\tif (is_null_oid(o))\n-\t\treturn 0;\n-\tif (is_null_oid(a))\n-\t\treturn 0;\n-\tif (is_null_oid(b))\n-\t\treturn 0;\n+\tif (is_null_oid(a) || is_null_oid(b))\n+\t\tBUG(\"submodule deleted on one side; this should be handled outside of merge_submodule()\");\n \n-\tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n+\tif ((sub_not_initialized = repo_submodule_init(&subrepo,\n+\t\topt->repo, path, null_oid()))) {\n \t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n \t\t\t path);\n-\t\treturn 0;\n+\t\tsub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (is_null_oid(o)) {\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s (no merge base)\"),\n+\t\t\t path);\n+\t\tgoto cleanup;\n \t}\n \n \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n@@ -1771,6 +1801,7 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s (commits not present)\"),\n \t\t\t path);\n+\t\tsub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE;\n \t\tgoto cleanup;\n \t}\n \n@@ -1849,7 +1880,23 @@ static int merge_submodule(struct merge_options *opt,\n \n \tobject_array_clear(&merges);\n cleanup:\n-\trepo_clear(&subrepo);\n+\tif (!opt->priv->call_depth && !ret) {\n+\t\tstruct string_list *csub = &opt->priv->conflicted_submodules;\n+\t\tstruct conflicted_submodule_item *util;\n+\t\tconst char *abbrev;\n+\n+\t\tutil = xmalloc(sizeof(*util));\n+\t\tutil->flag = sub_flag;\n+\t\tutil->abbrev = NULL;\n+\t\tif (!sub_not_initialized) {\n+\t\t\tabbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);\n+\t\t\tutil->abbrev = xstrdup(abbrev);\n+\t\t}\n+\t\tstring_list_append(csub, path)->util = util;\n+\t}\n+\n+\tif (!sub_not_initialized)\n+\t\trepo_clear(&subrepo);\n \treturn ret;\n }\n \n@@ -4412,6 +4459,99 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n+\tstruct strbuf tmp = STRBUF_INIT;\n+\tstruct string_list msg_list = STRING_LIST_INIT_DUP;\n+\tint i;\n+\n+\tstring_list_split(&msg_list, msg->buf, '\\n', -1);\n+\tfor (i = 0; i < msg_list.nr; i++) {\n+\t\tif (!i)\n+\t\t\t/*\n+\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n+\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n+\t\t\t * languages, the following swap is suggested:\n+\t\t\t *      \" - %s\\n\" -> \"%s - \\n\"\n+\t\t\t */\n+\t\t\tstrbuf_addf(&tmp, _(\" - %s\\n\"), msg_list.items[i].string);\n+\t\telse\n+\t\t\t/*\n+\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n+\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n+\t\t\t * languages, the following swap is suggested:\n+\t\t\t *      \"   %s\\n\" -> \"%s   \\n\"\n+\t\t\t */\n+\t\t\tstrbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n+\t}\n+\tstrbuf_reset(msg);\n+\tstrbuf_add(msg, tmp.buf, tmp.len);\n+}\n+\n+static void print_submodule_conflict_suggestion(struct string_list *csub) {\n+\tstruct string_list_item *item;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tstruct strbuf tmp = STRBUF_INIT;\n+\tstruct strbuf subs = STRBUF_INIT;\n+\n+\tif (!csub->nr)\n+\t\treturn;\n+\t\n+\t/*\n+\t * NEEDSWORK: The steps to resolve these errors deserve a more\n+\t * detailed explanation than what is currently printed below.\n+\t */\n+\tfor_each_string_list_item(item, csub) {\n+\t\tstruct conflicted_submodule_item *util = item->util;\n+\n+\t\tif (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED ||\n+\t\t\tutil->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE)\n+\t\t\treturn;\n+\t}\n+\n+\tprintf(_(\"Recursive merging with submodules currently only supports \"\n+\t\t\"trivial cases.\\nPlease manually handle the merging of each \"\n+\t\t\"conflicted submodule.\\nThis can be accomplished with the following \"\n+\t\t\"steps:\"));\n+\tputchar('\\n');\n+\n+\tfor_each_string_list_item(item, csub) {\n+\t\tstruct conflicted_submodule_item *util = item->util;\n+\t\t/*\n+\t\t * TRANSLATORS: This is a line of advice to resolve a merge conflict\n+\t\t * in a submodule. The second argument is the abbreviated id of the\n+\t\t * commit that needs to be merged.\n+\t\t * E.g. - go to submodule (sub), and either merge commit abc1234\"\n+\t\t */\n+\t\tstrbuf_addf(&tmp, _(\"go to submodule (%s), and either merge commit %s\\n\"\n+\t\t\t\"or update to an existing commit which has merged those changes\"),\n+\t\t\titem->string, util->abbrev);\n+\t\tformat_submodule_conflict_suggestion(&tmp);\n+\t\tstrbuf_add(&msg, tmp.buf, tmp.len);\n+\t\tstrbuf_reset(&tmp);\n+\t}\n+\tstrbuf_add_separated_string_list(&subs, \" \", csub);\n+\tstrbuf_addstr(&tmp, _(\"come back to superproject and run:\"));\n+\tstrbuf_addf(&tmp, \"\\n\\ngit add %s\\n\\n\", subs.buf);\n+\tstrbuf_addstr(&tmp, _(\"to record the above merge or update\"));\n+\tformat_submodule_conflict_suggestion(&tmp);\n+\tstrbuf_add(&msg, tmp.buf, tmp.len);\n+\tstrbuf_reset(&tmp);\n+\n+\tstrbuf_addstr(&tmp, _(\"resolve any other conflicts in the superproject\"));\n+\tformat_submodule_conflict_suggestion(&tmp);\n+\tstrbuf_add(&msg, tmp.buf, tmp.len);\n+\tstrbuf_reset(&tmp);\n+\n+\tstrbuf_addstr(&tmp, _(\"commit the resulting index in the superproject\"));\n+\tformat_submodule_conflict_suggestion(&tmp);\n+\tstrbuf_add(&msg, tmp.buf, tmp.len);\n+\n+\tprintf(\"%s\", msg.buf);\n+\tstrbuf_release(&subs);\n+\tstrbuf_release(&tmp);\n+\tstrbuf_release(&msg);\n+}\n+\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result)\n@@ -4461,6 +4601,8 @@ void merge_display_update_messages(struct merge_options *opt,\n \t}\n \tstring_list_clear(&olist, 0);\n \n+\tprint_submodule_conflict_suggestion(&opti->conflicted_submodules);\n+\n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t       opti->renames.needed_limit, 0);\n@@ -4657,6 +4799,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n \ttrace2_region_enter(\"merge\", \"allocate/init\", opt->repo);\n \tif (opt->priv) {\n \t\tclear_or_reinit_internal_opts(opt->priv, 1);\n+\t\tstring_list_init_nodup(&opt->priv->conflicted_submodules);\n \t\ttrace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n \t\treturn;\n \t}\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex c253bf759a..414597a420 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '\n \t echo \"file-c\" > file-c &&\n \t git add file-c &&\n \t git commit -m \"sub-c\") &&\n-\tgit commit -a -m \"c\" &&\n+\tgit commit -a -m \"c\")\n+'\n \n+test_expect_success 'merging should conflict for non fast-forward' '\n+\ttest_when_finished \"git -C merge-search reset --hard\" &&\n+\t(cd merge-search &&\n+\t git checkout -b test-nonforward-a b &&\n+\t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+\t  then\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n+\t  else\n+\t\ttest_must_fail git merge c 2> actual\n+\t  fi)\n+'\n+\n+test_expect_success 'finish setup for merge-search' '\n+\t(cd merge-search &&\n \tgit checkout -b d a &&\n \t(cd sub &&\n \t git checkout -b sub-d sub-b &&\n@@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' '\n \t test_cmp expect actual)\n '\n \n-test_expect_success 'merging should conflict for non fast-forward' '\n+test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '\n \t(cd merge-search &&\n-\t git checkout -b test-nonforward b &&\n+\t git checkout -b test-nonforward-b b &&\n \t (cd sub &&\n \t  git rev-parse --short sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t \tgrep \"$sub_expect\" actual\n \t  else\n \t\ttest_must_fail git merge c 2> actual\n \t  fi &&\n@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '\n \t ) &&\n \t if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t then\n-\t\ttest_must_fail git merge c >actual\n+\t\ttest_must_fail git merge c >actual &&\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)\" &&\n+\t\tgrep \"$sub_expect\" actual\n \t else\n \t\ttest_must_fail git merge c 2> actual\n \t fi &&\n@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '\n \tgit commit -a -m \"f\" &&\n \n \tgit checkout -b test-backward e &&\n-\ttest_must_fail git merge f)\n+\ttest_must_fail git merge f >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n '\n \n \n@@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward\n \t)\n '\n \n+# Setup:\n+#   - Submodule has 2 commits: a and b\n+#   - Superproject branch 'a' adds and commits submodule pointing to 'commit a'\n+#   - Superproject branch 'b' adds and commits submodule pointing to 'commit b'\n+# If these two branches are now merged, there is no merge base\n+test_expect_success 'setup for null merge base' '\n+\tmkdir no-merge-base &&\n+\t(cd no-merge-base &&\n+\tgit init &&\n+\tmkdir sub &&\n+\t(cd sub &&\n+\t git init &&\n+\t echo \"file-a\" > file-a &&\n+\t git add file-a &&\n+\t git commit -m \"commit a\") &&\n+\tgit commit --allow-empty -m init &&\n+\tgit branch init &&\n+\tgit checkout -b a init &&\n+\tgit add sub &&\n+\tgit commit -m \"a\" &&\n+\tgit switch main &&\n+\t(cd sub &&\n+\t echo \"file-b\" > file-b &&\n+\t git add file-b &&\n+\t git commit -m \"commit b\"))\n+'\n+\n+test_expect_success 'merging should fail with no merge base' '\n+\t(cd no-merge-base &&\n+\tgit checkout -b b init &&\n+\tgit add sub &&\n+\tgit commit -m \"b\" &&\n+\ttest_must_fail git merge a >actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)\" &&\n+\t\tgrep \"$sub_expect\" actual\n+\tfi)\n+'\n+\n test_done\ndiff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh\nindex 8e32f19007..ebeca12a71 100755\n--- a/t/t7402-submodule-rebase.sh\n+++ b/t/t7402-submodule-rebase.sh\n@@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' '\n \ttest_tick &&\n \tgit commit -m fourth &&\n \n-\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&\n+\ttest_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&\n \tgit ls-files -s submodule >actual &&\n \t(\n \t\tcd submodule &&\n@@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' '\n \t\techo \"160000 $(git rev-parse HEAD^^) 2\tsubmodule\" &&\n \t\techo \"160000 $(git rev-parse HEAD) 3\tsubmodule\"\n \t) >expect &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\tif test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n+    then\n+\t\tsub_expect=\"go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)\" &&\n+\t\tgrep \"$sub_expect\" actual_output\n+\tfi\n '\n \n test_done\n\nbase-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a\n-- \n2.37.1.559.g78731f0fdb-goog\n\n"},{"id":"461291","messageId":"xmqqczd0xjrp.fsf@gitster.g","threadId":"57963","inReplyTo":"20220804195105.1303455-1-calvinwan@google.com","subject":"Re: [PATCH v8] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-16T15:58:34Z","receivedAt":"2022-08-16T16:00:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> @@ -4412,6 +4459,99 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>  \treturn errs;\n>  }\n>  \n> +static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n> +\tstruct strbuf tmp = STRBUF_INIT;\n> +\tstruct string_list msg_list = STRING_LIST_INIT_DUP;\n> +\tint i;\n> +\n> +\tstring_list_split(&msg_list, msg->buf, '\\n', -1);\n> +\tfor (i = 0; i < msg_list.nr; i++) {\n> +\t\tif (!i)\n> +\t\t\t/*\n> +\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n> +\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n> +\t\t\t * languages, the following swap is suggested:\n> +\t\t\t *      \" - %s\\n\" -> \"%s - \\n\"\n> +\t\t\t */\n> +\t\t\tstrbuf_addf(&tmp, _(\" - %s\\n\"), msg_list.items[i].string);\n> +\t\telse\n> +\t\t\t/*\n> +\t\t\t * TRANSLATORS: This is line item of submodule conflict message\n> +\t\t\t * from print_submodule_conflict_suggestion() below. For RTL\n> +\t\t\t * languages, the following swap is suggested:\n> +\t\t\t *      \"   %s\\n\" -> \"%s   \\n\"\n> +\t\t\t */\n> +\t\t\tstrbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n> +\t}\n> +\tstrbuf_reset(msg);\n> +\tstrbuf_add(msg, tmp.buf, tmp.len);\n> +}\n\nHere, tmp is not released, and mst_list holds the words split out of msg.\n\n merge-ort.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git i/merge-ort.c w/merge-ort.c\nindex 4cb92bdff8..cdb53770be 100644\n--- i/merge-ort.c\n+++ w/merge-ort.c\n@@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n \t}\n \tstrbuf_reset(msg);\n \tstrbuf_add(msg, tmp.buf, tmp.len);\n+\tstring_list_clear(&msg_list, 0);\n+\tstrbuf_release(&tmp);\n }\n \n static void print_submodule_conflict_suggestion(struct string_list *csub) {\n"},{"id":"461306","messageId":"xmqqk078t3r6.fsf@gitster.g","threadId":"57963","inReplyTo":"xmqqczd0xjrp.fsf@gitster.g","subject":"Re: [PATCH v8] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-16T18:58:05Z","receivedAt":"2022-08-16T18:58:16Z","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>> +\t\t\t * languages, the following swap is suggested:\n>> +\t\t\t *      \"   %s\\n\" -> \"%s   \\n\"\n>> +\t\t\t */\n>> +\t\t\tstrbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n>> +\t}\n>> +\tstrbuf_reset(msg);\n>> +\tstrbuf_add(msg, tmp.buf, tmp.len);\n>> +}\n>\n> Here, tmp is not released, and mst_list holds the words split out of msg.\n\nFWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI\njob.\n\n>  merge-ort.c | 2 ++\n>  1 file changed, 2 insertions(+)\n>\n> diff --git i/merge-ort.c w/merge-ort.c\n> index 4cb92bdff8..cdb53770be 100644\n> --- i/merge-ort.c\n> +++ w/merge-ort.c\n> @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n>  \t}\n>  \tstrbuf_reset(msg);\n>  \tstrbuf_add(msg, tmp.buf, tmp.len);\n> +\tstring_list_clear(&msg_list, 0);\n> +\tstrbuf_release(&tmp);\n>  }\n>  \n>  static void print_submodule_conflict_suggestion(struct string_list *csub) {\n"},{"id":"461307","messageId":"CAFySSZCNVBiSXqvkmGkRoD6-+KL4cac9yZWDx=xyTSmNhhEc-Q@mail.gmail.com","threadId":"57963","inReplyTo":"xmqqk078t3r6.fsf@gitster.g","subject":"Re: [PATCH v8] submodule merge: update conflict error message","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2022-08-16T19:34:35Z","receivedAt":"2022-08-16T19:34:54Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Should I re-roll with just that change?\n\nOn Tue, Aug 16, 2022 at 11:58 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> >> +                     * languages, the following swap is suggested:\n> >> +                     *      \"   %s\\n\" -> \"%s   \\n\"\n> >> +                     */\n> >> +                    strbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n> >> +    }\n> >> +    strbuf_reset(msg);\n> >> +    strbuf_add(msg, tmp.buf, tmp.len);\n> >> +}\n> >\n> > Here, tmp is not released, and mst_list holds the words split out of msg.\n>\n> FWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI\n> job.\n>\n> >  merge-ort.c | 2 ++\n> >  1 file changed, 2 insertions(+)\n> >\n> > diff --git i/merge-ort.c w/merge-ort.c\n> > index 4cb92bdff8..cdb53770be 100644\n> > --- i/merge-ort.c\n> > +++ w/merge-ort.c\n> > @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n> >       }\n> >       strbuf_reset(msg);\n> >       strbuf_add(msg, tmp.buf, tmp.len);\n> > +     string_list_clear(&msg_list, 0);\n> > +     strbuf_release(&tmp);\n> >  }\n> >\n> >  static void print_submodule_conflict_suggestion(struct string_list *csub) {\n"},{"id":"461309","messageId":"xmqqa684t1tl.fsf@gitster.g","threadId":"57963","inReplyTo":"CAFySSZCNVBiSXqvkmGkRoD6-+KL4cac9yZWDx=xyTSmNhhEc-Q@mail.gmail.com","subject":"Re: [PATCH v8] submodule merge: update conflict error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-16T19:39:50Z","receivedAt":"2022-08-16T19:39:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Calvin Wan <calvinwan@google.com> writes:\n\n> On Tue, Aug 16, 2022 at 11:58 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>> >> +                     * languages, the following swap is suggested:\n>> >> +                     *      \"   %s\\n\" -> \"%s   \\n\"\n>> >> +                     */\n>> >> +                    strbuf_addf(&tmp, _(\"   %s\\n\"), msg_list.items[i].string);\n>> >> +    }\n>> >> +    strbuf_reset(msg);\n>> >> +    strbuf_add(msg, tmp.buf, tmp.len);\n>> >> +}\n>> >\n>> > Here, tmp is not released, and mst_list holds the words split out of msg.\n>>\n>> FWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI\n>> job.\n>>\n>> >  merge-ort.c | 2 ++\n>> >  1 file changed, 2 insertions(+)\n>> ...\n>\n> Should I re-roll with just that change?\n\nI think the topic is already in 'next', so no, it is too late for\nthat.\n\nI queued the attached on top but haven't merged to 'next' yet.\nMaking sure that there is no silly mistakes (like \"oh, no, that\ntemporary variable is still alive and it shouldn't be released there\njust yet\") by lending an extra pair of eyes is what is the most\nuseful at this point ;-)\n\n--- >8 ---\nSubject: [PATCH] merge-ort: plug leaks in \"submodule conflict suggestion\" code\n\nThe helper function uses two temporary variables that it forgets to\nrelease after it is done with them.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n merge-ort.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex a52faf6e21..f33df3ff65 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) {\n \t}\n \tstrbuf_reset(msg);\n \tstrbuf_add(msg, tmp.buf, tmp.len);\n+\tstring_list_clear(&msg_list, 0);\n+\tstrbuf_release(&tmp);\n }\n \n static void print_submodule_conflict_suggestion(struct string_list *csub) {\n-- \n2.37.2-492-g20f88697f3\n\n\n"}]}