* [PATCH] submodule merge: update conflict error message @ 2022-06-06 23:54 Calvin Wan 2022-06-07 0:48 ` Junio C Hamano 2022-06-10 23:11 ` [PATCH v2] " Calvin Wan 0 siblings, 2 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-06 23:54 UTC (permalink / raw) To: git; +Cc: chooglen, Calvin Wan When attempting to do a non-fast-forward merge in a project with conflicts in the submodules, the merge fails and git prints the following error: Failed to merge submodule <submodules> CONFLICT (submodule):Merge conflict in <submodule> Automatic merge failed; fix conflicts and then commit the result. Git is left in a conflicted state, which requires the user to: 1. abort the merge 2. merge submodules 3. merge superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to the following when attempting to do a non-fast-forward merge in a project with conflicts in the submodules. The error message is based off of what would happen when `merge --recurse-submodules` is eventually supported Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; recursive merging with submodules is currently not supported. To manually merge, the following steps are recommended: - abort the current merge - merge submodules individually - merge superproject I considered automatically aborting the merge if git detects the merge failed because of a submodule conflict, however, doing so causes a significant amount of tests in `t7610-mergetool.sh` (and some other test scripts as well) to fail, suggesting users have come to expect this state and have their workarounds with `git mergetool` Signed-off-by: Calvin Wan <calvinwan@google.com> --- builtin/merge.c | 12 +++++++++++- merge-ort.c | 4 +++- merge-recursive.c | 4 +++- merge-recursive.h | 1 + t/t6437-submodule-merge.sh | 5 ++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index f178f5a3ee..39f5ee66d6 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -88,6 +88,7 @@ static const char *sign_commit; static int autostash; static int no_verify; static char *into_name; +static int submodule_conflict = 0; static struct strategy all_strategy[] = { { "recursive", NO_TRIVIAL }, @@ -757,6 +758,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, else clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); + if (o.submodule_conflict) + submodule_conflict = 1; if (clean < 0) exit(128); if (write_locked_index(&the_index, &lock, @@ -973,7 +976,14 @@ static int suggest_conflicts(void) strbuf_release(&msgbuf); fclose(fp); repo_rerere(the_repository, allow_rerere_auto); - printf(_("Automatic merge failed; " + if (submodule_conflict) + printf(_("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")); + else + printf(_("Automatic merge failed; " "fix conflicts and then commit the result.\n")); return 1; } diff --git a/merge-ort.c b/merge-ort.c index 0d3f42592f..205d6658bc 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -3866,8 +3866,10 @@ static void process_entry(struct merge_options *opt, const char *reason = _("content"); if (ci->filemask == 6) reason = _("add/add"); - if (S_ISGITLINK(merged_file.mode)) + if (S_ISGITLINK(merged_file.mode)) { reason = _("submodule"); + opt->submodule_conflict = 1; + } path_msg(opt, path, 0, _("CONFLICT (%s): Merge conflict in %s"), reason, path); diff --git a/merge-recursive.c b/merge-recursive.c index fd1bbde061..535b8cc758 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3149,8 +3149,10 @@ static int handle_content_merge(struct merge_file_info *mfi, } if (!mfi->clean) { - if (S_ISGITLINK(mfi->blob.mode)) + if (S_ISGITLINK(mfi->blob.mode)) { reason = _("submodule"); + opt->submodule_conflict = 1; + } output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), reason, path); if (ci && !df_conflict_remains) diff --git a/merge-recursive.h b/merge-recursive.h index b88000e3c2..6fd31644ad 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -51,6 +51,7 @@ struct merge_options { /* internal fields used by the implementation */ struct merge_options_internal *priv; + unsigned submodule_conflict : 1; }; void init_merge_options(struct merge_options *opt, struct repository *repo); diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 178413c22f..bfcc81cd06 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' test_must_fail git merge c 2> actual fi && grep $(cat expect) actual > /dev/null && + test_i18ngrep "recursive merging with submodules is currently" actual && git reset --hard) ' @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' fi && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && + test_i18ngrep "recursive merging with submodules is currently" actual && git reset --hard) ' @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + test_i18ngrep "recursive merging with submodules is currently" actual) ' base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 -- 2.36.1.255.ge46751e96f-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH] submodule merge: update conflict error message 2022-06-06 23:54 [PATCH] submodule merge: update conflict error message Calvin Wan @ 2022-06-07 0:48 ` Junio C Hamano 2022-06-08 17:19 ` Calvin Wan 2022-06-10 23:11 ` [PATCH v2] " Calvin Wan 1 sibling, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-06-07 0:48 UTC (permalink / raw) To: Calvin Wan; +Cc: git, chooglen Calvin Wan <calvinwan@google.com> writes: > When attempting to do a non-fast-forward merge in a project with > conflicts in the submodules, the merge fails and git prints the > following error: > > Failed to merge submodule <submodules> > CONFLICT (submodule):Merge conflict in <submodule> > Automatic merge failed; fix conflicts and then commit the result. > > Git is left in a conflicted state, which requires the user to: > 1. abort the merge > 2. merge submodules > 3. merge superproject > These steps are non-obvious for newer submodule users to figure out Hmph. Is 1. necessary? IOW, based on the information we already have (we may not be surfacing, which can be corrected), wouldn't it be easier to instead (A) go to submodule and make a merge and then (B) come back to the superproject, "git add <submodule" to record the result of submodule merge, and say "git commit" to conclude? The thing I am worried most about is that you may be throwing away information that would help the user by aborting the superproject merge. Before doing so, you have stage #2 and stage #3 of the submodule commit, so which commits in the submodule you need to merge in (A) above should be fairly clear. If you abort the merge first, how does the user know which commits in the submodule the user needs to merge? > The error message is based off of what would happen when `merge > --recurse-submodules` is eventually supported OK. > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Automatic merge failed; recursive merging with submodules is currently > not supported. To manually merge, the following steps are recommended: > - abort the current merge > - merge submodules individually > - merge superproject Again, I am not sure about the recommendation. The message saying "currently not supported" I think is a good idea. > I considered automatically aborting the merge if git detects the merge > failed because of a submodule conflict, however, doing so causes a > significant amount of tests in `t7610-mergetool.sh` (and some other test > scripts as well) to fail, suggesting users have come to expect this > state and have their workarounds with `git mergetool` With or without test failures, my gut feeling sais that it cannot be a good idea to automatically abort the merge, without first grabbing some information out of the conflicted state. Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH] submodule merge: update conflict error message 2022-06-07 0:48 ` Junio C Hamano @ 2022-06-08 17:19 ` Calvin Wan 2022-06-08 17:34 ` Glen Choo 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-06-08 17:19 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, chooglen > Hmph. Is 1. necessary? I just tested it and it is not, so I do agree recommending to abort the merge is unnecessary/bad advice. How does this sound? Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; recursive merging with submodules is currently not supported. To manually merge, merge conflicted submodules first before merging the superproject. On Mon, Jun 6, 2022 at 5:48 PM Junio C Hamano <gitster@pobox.com> wrote: > > Calvin Wan <calvinwan@google.com> writes: > > > When attempting to do a non-fast-forward merge in a project with > > conflicts in the submodules, the merge fails and git prints the > > following error: > > > > Failed to merge submodule <submodules> > > CONFLICT (submodule):Merge conflict in <submodule> > > Automatic merge failed; fix conflicts and then commit the result. > > > > Git is left in a conflicted state, which requires the user to: > > 1. abort the merge > > 2. merge submodules > > 3. merge superproject > > These steps are non-obvious for newer submodule users to figure out > > Hmph. Is 1. necessary? > > IOW, based on the information we already have (we may not be > surfacing, which can be corrected), wouldn't it be easier to instead > (A) go to submodule and make a merge and then (B) come back to the > superproject, "git add <submodule" to record the result of submodule > merge, and say "git commit" to conclude? > > The thing I am worried most about is that you may be throwing away > information that would help the user by aborting the superproject > merge. Before doing so, you have stage #2 and stage #3 of the > submodule commit, so which commits in the submodule you need to > merge in (A) above should be fairly clear. If you abort the merge > first, how does the user know which commits in the submodule the > user needs to merge? > > > The error message is based off of what would happen when `merge > > --recurse-submodules` is eventually supported > > OK. > > > Failed to merge submodule <submodule> > > CONFLICT (submodule): Merge conflict in <submodule> > > Automatic merge failed; recursive merging with submodules is currently > > not supported. To manually merge, the following steps are recommended: > > - abort the current merge > > - merge submodules individually > > - merge superproject > > Again, I am not sure about the recommendation. The message saying > "currently not supported" I think is a good idea. > > > I considered automatically aborting the merge if git detects the merge > > failed because of a submodule conflict, however, doing so causes a > > significant amount of tests in `t7610-mergetool.sh` (and some other test > > scripts as well) to fail, suggesting users have come to expect this > > state and have their workarounds with `git mergetool` > > With or without test failures, my gut feeling sais that it cannot be > a good idea to automatically abort the merge, without first grabbing > some information out of the conflicted state. > > Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH] submodule merge: update conflict error message 2022-06-08 17:19 ` Calvin Wan @ 2022-06-08 17:34 ` Glen Choo 2022-06-08 18:01 ` Calvin Wan 0 siblings, 1 reply; 60+ messages in thread From: Glen Choo @ 2022-06-08 17:34 UTC (permalink / raw) To: Calvin Wan, Junio C Hamano; +Cc: git Calvin Wan <calvinwan@google.com> writes: >> Hmph. Is 1. necessary? > > I just tested it and it is not, so I do agree recommending to abort the > merge is unnecessary/bad advice. How does this sound? > > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Automatic merge failed; recursive merging with submodules is currently > not supported. To manually merge, merge conflicted submodules first > before merging the superproject. This message sounds ok to me, since this is probably what the user wants 90% of the time. Since we don't abort the merge, this just a 'regular' merge conflict resolution (albeit with submodules). The user probably wants to merge the submodules, but they can choose however they want to resolve the merge conflict, e.g. maybe they'd prefer to just pick one side (or even more exotically, a different commit altogether.) An improvement for that other 10% would be to print this help message with the advice() API so that users can turn it off if they don't find it helpful. Or maybe it's confusing to some new users who use a different merging workflow and so an admin might turn off this advice for them. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH] submodule merge: update conflict error message 2022-06-08 17:34 ` Glen Choo @ 2022-06-08 18:01 ` Calvin Wan 2022-06-08 19:13 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-06-08 18:01 UTC (permalink / raw) To: Glen Choo; +Cc: Junio C Hamano, git > The user probably > wants to merge the submodules, but they can choose however they want to > resolve the merge conflict It sounds like I should reword "merge conflicted submodules" to "resolve conflicted submodules". That should cover those 10% cases. I would prefer to find a generic, but still helpful message that doesn't require going into the advice() API or require some config change On Wed, Jun 8, 2022 at 10:35 AM Glen Choo <chooglen@google.com> wrote: > > Calvin Wan <calvinwan@google.com> writes: > > >> Hmph. Is 1. necessary? > > > > I just tested it and it is not, so I do agree recommending to abort the > > merge is unnecessary/bad advice. How does this sound? > > > > Failed to merge submodule <submodule> > > CONFLICT (submodule): Merge conflict in <submodule> > > Automatic merge failed; recursive merging with submodules is currently > > not supported. To manually merge, merge conflicted submodules first > > before merging the superproject. > > This message sounds ok to me, since this is probably what the user wants > 90% of the time. Since we don't abort the merge, this just a 'regular' > merge conflict resolution (albeit with submodules). The user probably > wants to merge the submodules, but they can choose however they want to > resolve the merge conflict, e.g. maybe they'd prefer to just pick one > side (or even more exotically, a different commit altogether.) > > An improvement for that other 10% would be to print this help message > with the advice() API so that users can turn it off if they don't find > it helpful. Or maybe it's confusing to some new users who use a > different merging workflow and so an admin might turn off this advice > for them. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH] submodule merge: update conflict error message 2022-06-08 18:01 ` Calvin Wan @ 2022-06-08 19:13 ` Junio C Hamano 0 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-06-08 19:13 UTC (permalink / raw) To: Calvin Wan; +Cc: Glen Choo, git Calvin Wan <calvinwan@google.com> writes: >> The user probably >> wants to merge the submodules, but they can choose however they want to >> resolve the merge conflict > > It sounds like I should reword "merge conflicted submodules" to > "resolve conflicted submodules". That should cover those 10% cases. > > I would prefer to find a generic, but still helpful message that doesn't > require going into the advice() API or require some config change We want the users not to blow away the half-merged state in the working tree. We are guiding them to first go into submodules and merge (in which case, we should tell them merge what with what---I think the first parent should be what they have checked out there, but the other parent, which is what is recorded in the tree of the superproject commit being merged as gitlink, may not be at the tip of any branch you have in the submodule). And then they come back to the superproject and resolve the conflict in the working tree and the index. > > Failed to merge submodule <submodule> > > CONFLICT (submodule): Merge conflict in <submodule> > > Automatic merge failed; recursive merging with submodules is currently > > not supported. To manually merge, merge conflicted submodules first > > before merging the superproject. So, to manually complete the merge: - go to submodule A, and merge commit a24c4e37d0 - go to submodule B, and merge commit a6f14c960b - come back to superproject, and "git add A B" to record the above merge - in superproject, resolve the other conflicts - commit the resulting index in the superproject or something along that line, perhaps? ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v2] submodule merge: update conflict error message 2022-06-06 23:54 [PATCH] submodule merge: update conflict error message Calvin Wan 2022-06-07 0:48 ` Junio C Hamano @ 2022-06-10 23:11 ` Calvin Wan 2022-06-11 4:53 ` Elijah Newren 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan 1 sibling, 2 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-10 23:11 UTC (permalink / raw) To: git; +Cc: chooglen, gitster, Calvin Wan When attempting to do a non-fast-forward merge in a project with conflicts in the submodules, the merge fails and git prints the following error: Failed to merge submodule <submodules> CONFLICT (submodule):Merge conflict in <submodule> Automatic merge failed; fix conflicts and then commit the result. Git is left in a conflicted state, which requires the user to: 1. merge submodules 2. add submodules changes to the superproject 3. merge superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to the following when attempting to do a non-fast-forward merge in a project with conflicts in the submodules. The error message is based off of what would happen when `merge --recurse-submodules` is eventually supported Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; recursive merging with submodules is currently not supported. To manually complete the merge: - go to submodule (<submodule>), and merge commit <commit> - come back to superproject, and `git add <submodule>` to record the above merge - resolve any other conflicts in the superproject - commit the resulting index in the superproject Changes since v1: - Removed advice to abort merge - Error message updated to contain more commit/submodule information Signed-off-by: Calvin Wan <calvinwan@google.com> --- builtin/merge.c | 23 ++++++++++++++++++++++- merge-ort.c | 7 ++++++- merge-recursive.c | 7 ++++++- merge-recursive.h | 4 ++++ t/t6437-submodule-merge.sh | 5 ++++- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index f178f5a3ee..7e2deea7fb 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -88,6 +88,8 @@ static const char *sign_commit; static int autostash; static int no_verify; static char *into_name; +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; static struct strategy all_strategy[] = { { "recursive", NO_TRIVIAL }, @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, } init_merge_options(&o, the_repository); + o.conflicted_submodule_oids = &conflicted_submodule_oids; + o.conflicted_submodule_paths = &conflicted_submodule_paths; if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; @@ -973,8 +977,25 @@ static int suggest_conflicts(void) strbuf_release(&msgbuf); fclose(fp); repo_rerere(the_repository, allow_rerere_auto); - printf(_("Automatic merge failed; " + if (conflicted_submodule_oids.nr > 0) { + int i; + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" + "not supported. To manually complete the merge:\n")); + for (i = 0; i < conflicted_submodule_oids.nr; i++) { + printf(_(" - go to submodule (%s), and merge commit %s\n"), + conflicted_submodule_paths.items[i].string, + oid_to_hex(&conflicted_submodule_oids.oid[i])); + } + printf(_(" - come back to superproject, and `git add")); + for (i = 0; i < conflicted_submodule_paths.nr; i++) + printf(_(" %s"), conflicted_submodule_paths.items[i].string); + printf(_("` to record the above merge \n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n")); + } else { + printf(_("Automatic merge failed; " "fix conflicts and then commit the result.\n")); + } return 1; } diff --git a/merge-ort.c b/merge-ort.c index 0d3f42592f..c86ee11614 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, const char *reason = _("content"); if (ci->filemask == 6) reason = _("add/add"); - if (S_ISGITLINK(merged_file.mode)) + if (S_ISGITLINK(merged_file.mode)) { reason = _("submodule"); + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); + string_list_append(opt->conflicted_submodule_paths, path); + } + } path_msg(opt, path, 0, _("CONFLICT (%s): Merge conflict in %s"), reason, path); diff --git a/merge-recursive.c b/merge-recursive.c index fd1bbde061..ff7cdbefe9 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, } if (!mfi->clean) { - if (S_ISGITLINK(mfi->blob.mode)) + if (S_ISGITLINK(mfi->blob.mode)) { reason = _("submodule"); + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); + string_list_append(opt->conflicted_submodule_paths, path); + } + } output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), reason, path); if (ci && !df_conflict_remains) diff --git a/merge-recursive.h b/merge-recursive.h index b88000e3c2..5d267e7a43 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -51,6 +51,10 @@ struct merge_options { /* internal fields used by the implementation */ struct merge_options_internal *priv; + + /* fields that hold submodule conflict information */ + struct oid_array *conflicted_submodule_oids; + struct string_list *conflicted_submodule_paths; }; void init_merge_options(struct merge_options *opt, struct repository *repo); diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 178413c22f..5b384dedc1 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' test_must_fail git merge c 2> actual fi && grep $(cat expect) actual > /dev/null && + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && git reset --hard) ' @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' fi && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && git reset --hard) ' @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) ' -- 2.36.1.476.g0c4daa206d-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-10 23:11 ` [PATCH v2] " Calvin Wan @ 2022-06-11 4:53 ` Elijah Newren 2022-06-11 17:08 ` Philippe Blain ` (2 more replies) 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan 1 sibling, 3 replies; 60+ messages in thread From: Elijah Newren @ 2022-06-11 4:53 UTC (permalink / raw) To: Calvin Wan; +Cc: Git Mailing List, Glen Choo, Junio C Hamano On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote: > > When attempting to do a non-fast-forward merge in a project with > conflicts in the submodules, the merge fails and git prints the > following error: > > Failed to merge submodule <submodules> > CONFLICT (submodule):Merge conflict in <submodule> > Automatic merge failed; fix conflicts and then commit the result. > > Git is left in a conflicted state, which requires the user to: > 1. merge submodules > 2. add submodules changes to the superproject > 3. merge superproject I think we may need to tweak these steps a bit: 1. merge submodules OR update submodules to an already existing commit that reflects the merge (i.e. as submodules they may well be independently actively developed. Someone may have already merged the appropriate branch/commit and the already extant merges should be used in preference to creating new ones.) 2. <just as you said> 3. FINISH merging the superproject (i.e. don't redo the merge) I might be off on step 1; I have only used submodules extremely lightly and usually only for a limited time, so I'm not really sure what the expected workflow is. I could also imagine it potentially being repository-dependent whether you would want to merge or select an appropriate commit to update to. > These steps are non-obvious for newer submodule users to figure out > based on the error message and neither `git submodule status` nor `git > status` provide any useful pointers. > > Update error message to the following when attempting to do a > non-fast-forward merge in a project with conflicts in the submodules. Make sense. > The error message is based off of what would happen when `merge > --recurse-submodules` is eventually supported > > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Automatic merge failed; recursive merging with submodules is currently > not supported. To manually complete the merge: > - go to submodule (<submodule>), and merge commit <commit> > - come back to superproject, and `git add <submodule>` to record the above merge > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject Ah, I see you've fixed step 3 here; that's good. However, these steps miss out on the merge-or-update submodule possibility...and since you mention these steps are potentially the basis for some future work, I think it's worth calling that out again. I'm slightly worried that the 'update' part of merge-or-update may throw a wrench in the plans for `merge --recurse-submodules`. (Also, continuing on the `merge --recurse-submodules` talent but discussing a different aspect of it, I'm curious if you need to add extra dirty-worktree/dirty-index checks for each submodule at the start of a merge, whether you need to try to lock N indexes before starting, and what other extra details are necessary. But those are probably questions to address whenever work on the future series to implement this option is underway.) > Changes since v1: > - Removed advice to abort merge > - Error message updated to contain more commit/submodule information > > Signed-off-by: Calvin Wan <calvinwan@google.com> > > --- > builtin/merge.c | 23 ++++++++++++++++++++++- > merge-ort.c | 7 ++++++- > merge-recursive.c | 7 ++++++- > merge-recursive.h | 4 ++++ > t/t6437-submodule-merge.sh | 5 ++++- > 5 files changed, 42 insertions(+), 4 deletions(-) So you're modifying the "git merge" porcelain level (builtin/merge.c), the two merges strategies, their common header, and adding some tests. No other porcelains are modified... > diff --git a/builtin/merge.c b/builtin/merge.c > index f178f5a3ee..7e2deea7fb 100644 > --- a/builtin/merge.c > +++ b/builtin/merge.c > @@ -88,6 +88,8 @@ static const char *sign_commit; > static int autostash; > static int no_verify; > static char *into_name; > +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; > +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; > > static struct strategy all_strategy[] = { > { "recursive", NO_TRIVIAL }, > @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, > } > > init_merge_options(&o, the_repository); > + o.conflicted_submodule_oids = &conflicted_submodule_oids; > + o.conflicted_submodule_paths = &conflicted_submodule_paths; > if (!strcmp(strategy, "subtree")) > o.subtree_shift = ""; > > @@ -973,8 +977,25 @@ static int suggest_conflicts(void) > strbuf_release(&msgbuf); > fclose(fp); > repo_rerere(the_repository, allow_rerere_auto); > - printf(_("Automatic merge failed; " > + if (conflicted_submodule_oids.nr > 0) { > + int i; > + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" > + "not supported. To manually complete the merge:\n")); > + for (i = 0; i < conflicted_submodule_oids.nr; i++) { > + printf(_(" - go to submodule (%s), and merge commit %s\n"), > + conflicted_submodule_paths.items[i].string, > + oid_to_hex(&conflicted_submodule_oids.oid[i])); > + } > + printf(_(" - come back to superproject, and `git add")); > + for (i = 0; i < conflicted_submodule_paths.nr; i++) > + printf(_(" %s"), conflicted_submodule_paths.items[i].string); > + printf(_("` to record the above merge \n" > + " - resolve any other conflicts in the superproject\n" > + " - commit the resulting index in the superproject\n")); > + } else { > + printf(_("Automatic merge failed; " > "fix conflicts and then commit the result.\n")); > + } > return 1; > } This is kind of nice. I was worried you were going to embed these messages in the merge strategies, which could cause problems for other users of the merge strategies such as the --remerge-diff options to git log and git show (your new messages would be unwanted noise or even cause confusion there), and to the merge-tree work. In fact, a current submodule-merging message (search for "--cacheinfo") that is potentially similar to what you are adding here but which was added at the merge strategy level already feels highly problematic to me. I've been considering nuking it from the codebase for some time because of those issues, though I guess just moving it out elsewhere may also work. However, this implementation does have a drawback: these messages won't appear for rebases, cherry-picks, reverts, attempted unstashing (git stash apply/pop), or other actions unless you update the relevant porcelains for those as well. A possible alternative here would be to move it to the level of merge-recursive and merge-ort that is only called when the working tree and index are updated. For example, placing it in merge_finalize() in merge-recursive.c and merge_switch_to_result() in merge-ort.c -- next to the diff_warn_rename_limit() call in each case. However, I'm also fine with keeping it at the porcelain level, it just may need to be in a function that is called from several porcelains that way. > diff --git a/merge-ort.c b/merge-ort.c > index 0d3f42592f..c86ee11614 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, > const char *reason = _("content"); > if (ci->filemask == 6) > reason = _("add/add"); > - if (S_ISGITLINK(merged_file.mode)) > + if (S_ISGITLINK(merged_file.mode)) { > reason = _("submodule"); > + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); > + string_list_append(opt->conflicted_submodule_paths, path); > + } > + } > path_msg(opt, path, 0, > _("CONFLICT (%s): Merge conflict in %s"), > reason, path); > diff --git a/merge-recursive.c b/merge-recursive.c > index fd1bbde061..ff7cdbefe9 100644 > --- a/merge-recursive.c > +++ b/merge-recursive.c > @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, > } > > if (!mfi->clean) { > - if (S_ISGITLINK(mfi->blob.mode)) > + if (S_ISGITLINK(mfi->blob.mode)) { > reason = _("submodule"); > + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); > + string_list_append(opt->conflicted_submodule_paths, path); > + } > + } > output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), > reason, path); > if (ci && !df_conflict_remains) Nice that the changes needed to both the ort and recursive strategies are so localized. :-) > diff --git a/merge-recursive.h b/merge-recursive.h > index b88000e3c2..5d267e7a43 100644 > --- a/merge-recursive.h > +++ b/merge-recursive.h > @@ -51,6 +51,10 @@ struct merge_options { > > /* internal fields used by the implementation */ > struct merge_options_internal *priv; > + > + /* fields that hold submodule conflict information */ > + struct oid_array *conflicted_submodule_oids; > + struct string_list *conflicted_submodule_paths; > }; Make sense. > void init_merge_options(struct merge_options *opt, struct repository *repo); > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index 178413c22f..5b384dedc1 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' > test_must_fail git merge c 2> actual > fi && > grep $(cat expect) actual > /dev/null && > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > git reset --hard) > ' > > @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > fi && > grep $(cat expect1) actual > /dev/null && > grep $(cat expect2) actual > /dev/null && > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > git reset --hard) > ' > > @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' > git commit -a -m "f" && > > git checkout -b test-backward e && > - test_must_fail git merge f) > + test_must_fail git merge f >actual && > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) test_i18ngrep is apparently on the way out: $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh # Wrapper for grep which used to be used for # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other # in-flight changes. Should not be used and will be removed soon. test_i18ngrep () { I think you just want to use grep instead here for each of these hunks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-11 4:53 ` Elijah Newren @ 2022-06-11 17:08 ` Philippe Blain 2022-06-12 6:56 ` Elijah Newren 2022-06-13 2:03 ` Calvin Wan 2022-06-12 23:30 ` Junio C Hamano 2022-06-13 1:54 ` Calvin Wan 2 siblings, 2 replies; 60+ messages in thread From: Philippe Blain @ 2022-06-11 17:08 UTC (permalink / raw) To: Elijah Newren, Calvin Wan; +Cc: Git Mailing List, Glen Choo, Junio C Hamano Hi Calvin and Elijah, Le 2022-06-11 à 00:53, Elijah Newren a écrit : > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote: >> >> When attempting to do a non-fast-forward merge in a project with >> conflicts in the submodules, the merge fails and git prints the >> following error: I think we would want to be slightly more precise here, as "conflicts in the submodules" could be understood to mean: 1) conflicting submodule pointers in the superproject being merged 2) 1. + also content conflicts in the submodule merge Here we are just talking about 1. Also, the merge does not automatically fail, it only fails if a fast-forward *of the submodule pointer* is not possible, which might be what you meant above; but to me "a non-fast-forward merge in a project with conflict in the submodules" read like the non-fast-forwardness being talked about was in the superproject, not in the submodule(s). [message 0] >> >> Failed to merge submodule <submodules> >> CONFLICT (submodule):Merge conflict in <submodule> >> Automatic merge failed; fix conflicts and then commit the result. Aside: the first <submodules> should be singular. This is indeed the output you get with the ort strategy if no existing merge commit exist in the submodule repository that merges the submodule pointers recorded in the superproject branches being merged. With the older "recursive" strategy, this message is: [message 1] Failed to merge submodule sub (merge following commits not found) Auto-merging sub CONFLICT (submodule): Merge conflict in sub Automatic merge failed; fix conflicts and then commit the result c73cda76b1 (merge-ort: copy and adapt merge_submodule() from merge-recursive.c, 2021-01-01) does not mention why that error message was changed, but perhaps it is just because it is slightly confusing to the user (they might not be expecting Git to look for an existing merge and so they don't know what merge the message is talking about). Maybe something like "failed to find existing commit merging <hash1> and <hash2>" would be clearer... >> >> Git is left in a conflicted state, which requires the user to: >> 1. merge submodules >> 2. add submodules changes to the superproject >> 3. merge superproject > > I think we may need to tweak these steps a bit: > > 1. merge submodules OR update submodules to an already existing > commit that reflects the merge (i.e. as submodules they may well be > independently actively developed. Someone may have already merged the > appropriate branch/commit and the already extant merges should be used > in preference to creating new ones.) > 2. <just as you said> > 3. FINISH merging the superproject (i.e. don't redo the merge) > > I might be off on step 1; I have only used submodules extremely > lightly and usually only for a limited time, so I'm not really sure > what the expected workflow is. I could also imagine it potentially > being repository-dependent whether you would want to merge or select > an appropriate commit to update to. I agree with Elijah here, the submodule conflcit resolution might be to: 1) just choose one of the existing submodule commits on either side of the superproject branches being merged 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first) 3) create such a merge commit (what you are talking about here) I also agree that it is highly repository- and workflow-dependent what the "right" resolution is. Note that the code does try to find an existing merge commit in the submodule repository, in this case the error message is different. If such a merge commit exists: [message 2] Failed to merge submodule sub, but a possible merge resolution exists: aafcfa2 Merge branch 'sub-c' into sub-d If this is correct simply add it to the index for example by using: git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 "sub" which will accept this suggestion. CONFLICT (submodule): Merge conflict in sub Automatic merge failed; fix conflicts and then commit the result. if multiple merge exist: [message 3] Failed to merge submodule sub, but multiple possible merges exist: 2729a0c Merge branch 'sub-c' into ambiguous aafcfa2 Merge branch 'sub-c' into sub-d CONFLICT (submodule): Merge conflict in sub Automatic merge failed; fix conflicts and then commit the result. Another aside, I really don't think we should instruct users to run plumbing like 'git update-index --cacheinfo' , they should just cd into the submodule and checkout the merge commit! > >> These steps are non-obvious for newer submodule users to figure out >> based on the error message and neither `git submodule status` nor `git >> status` provide any useful pointers. >> >> Update error message to the following when attempting to do a >> non-fast-forward merge in a project with conflicts in the submodules. > > Make sense. I agree that more guidance is a very nice addition. Regarding 'git status' output, it is downright confusing, since it says: Unmerged paths: (use "git add <file>..." to mark resolution) both modified: sub which is not at all what you want to do most of the time (that would just stage whatever the currently checked out commit in the submodule is at the moment!) > >> The error message is based off of what would happen when `merge >> --recurse-submodules` is eventually supported >> >> Failed to merge submodule <submodule> >> CONFLICT (submodule): Merge conflict in <submodule> >> Automatic merge failed; recursive merging with submodules is currently >> not supported. To manually complete the merge: >> - go to submodule (<submodule>), and merge commit <commit> >> - come back to superproject, and `git add <submodule>` to record the above merge >> - resolve any other conflicts in the superproject >> - commit the resulting index in the superproject > > Ah, I see you've fixed step 3 here; that's good. > > However, these steps miss out on the merge-or-update submodule > possibility...and since you mention these steps are potentially the > basis for some future work, I think it's worth calling that out again. > I'm slightly worried that the 'update' part of merge-or-update may > throw a wrench in the plans for `merge --recurse-submodules`. > Slightly off topic here, but for me the most important improvement that 'git merge --recurse-submodules' would bring is when there is *no submodule conflicts*, i.e. one side fast-forwards the submodule and the other side does not touch it, since in that case the worktree of the submodule *is not updated* by the current code, which is one of the most confusing aspect of using submodules for new users ("why is "git status" and "git diff" not clean if "git merge" was fast-forward ?!?"), and the same is true (maybe more even so) for 'git rebase'. > (Also, continuing on the `merge --recurse-submodules` talent but > discussing a different aspect of it, I'm curious if you need to add > extra dirty-worktree/dirty-index checks for each submodule at the > start of a merge, whether you need to try to lock N indexes before > starting, and what other extra details are necessary. But those are > probably questions to address whenever work on the future series to > implement this option is underway.) > >> Changes since v1: >> - Removed advice to abort merge >> - Error message updated to contain more commit/submodule information >> >> Signed-off-by: Calvin Wan <calvinwan@google.com> >> >> --- >> builtin/merge.c | 23 ++++++++++++++++++++++- >> merge-ort.c | 7 ++++++- >> merge-recursive.c | 7 ++++++- >> merge-recursive.h | 4 ++++ >> t/t6437-submodule-merge.sh | 5 ++++- >> 5 files changed, 42 insertions(+), 4 deletions(-) > > So you're modifying the "git merge" porcelain level (builtin/merge.c), > the two merges strategies, their common header, and adding some tests. > No other porcelains are modified... > >> diff --git a/builtin/merge.c b/builtin/merge.c >> index f178f5a3ee..7e2deea7fb 100644 >> --- a/builtin/merge.c >> +++ b/builtin/merge.c >> @@ -88,6 +88,8 @@ static const char *sign_commit; >> static int autostash; >> static int no_verify; >> static char *into_name; >> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; >> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; >> >> static struct strategy all_strategy[] = { >> { "recursive", NO_TRIVIAL }, >> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, >> } >> >> init_merge_options(&o, the_repository); >> + o.conflicted_submodule_oids = &conflicted_submodule_oids; >> + o.conflicted_submodule_paths = &conflicted_submodule_paths; >> if (!strcmp(strategy, "subtree")) >> o.subtree_shift = ""; >> >> @@ -973,8 +977,25 @@ static int suggest_conflicts(void) >> strbuf_release(&msgbuf); >> fclose(fp); >> repo_rerere(the_repository, allow_rerere_auto); >> - printf(_("Automatic merge failed; " >> + if (conflicted_submodule_oids.nr > 0) { >> + int i; >> + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" >> + "not supported. To manually complete the merge:\n")); >> + for (i = 0; i < conflicted_submodule_oids.nr; i++) { >> + printf(_(" - go to submodule (%s), and merge commit %s\n"), >> + conflicted_submodule_paths.items[i].string, >> + oid_to_hex(&conflicted_submodule_oids.oid[i])); >> + } >> + printf(_(" - come back to superproject, and `git add")); >> + for (i = 0; i < conflicted_submodule_paths.nr; i++) >> + printf(_(" %s"), conflicted_submodule_paths.items[i].string); >> + printf(_("` to record the above merge \n" >> + " - resolve any other conflicts in the superproject\n" >> + " - commit the resulting index in the superproject\n")); >> + } else { >> + printf(_("Automatic merge failed; " >> "fix conflicts and then commit the result.\n")); >> + } >> return 1; >> } > > This is kind of nice. I was worried you were going to embed these > messages in the merge strategies, which could cause problems for other > users of the merge strategies such as the --remerge-diff options to > git log and git show (your new messages would be unwanted noise or > even cause confusion there), and to the merge-tree work. In fact, a > current submodule-merging message (search for "--cacheinfo") that is > potentially similar to what you are adding here but which was added at > the merge strategy level already feels highly problematic to me. I've > been considering nuking it from the codebase for some time because of > those issues, though I guess just moving it out elsewhere may also > work. > Yes, this is the message I copied above. I agree that if we can tweak this advice to instead mention 'git checkout' and add it to the message that Calvin is adding in this series, it would make for a really better UX. > However, this implementation does have a drawback: these messages > won't appear for rebases, cherry-picks, reverts, attempted unstashing > (git stash apply/pop), or other actions unless you update the relevant > porcelains for those as well. > > A possible alternative here would be to move it to the level of > merge-recursive and merge-ort that is only called when the working > tree and index are updated. For example, placing it in > merge_finalize() in merge-recursive.c and merge_switch_to_result() in > merge-ort.c -- next to the diff_warn_rename_limit() call in each case. > However, I'm also fine with keeping it at the porcelain level, it just > may need to be in a function that is called from several porcelains > that way. I think moving it to merge_finalize / merge_switch_to_result is indeed a good suggestion, then we might be improving the UX across the board and not just for 'git merge'. > >> diff --git a/merge-ort.c b/merge-ort.c >> index 0d3f42592f..c86ee11614 100644 >> --- a/merge-ort.c >> +++ b/merge-ort.c >> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, >> const char *reason = _("content"); >> if (ci->filemask == 6) >> reason = _("add/add"); >> - if (S_ISGITLINK(merged_file.mode)) >> + if (S_ISGITLINK(merged_file.mode)) { >> reason = _("submodule"); >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { >> + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); >> + string_list_append(opt->conflicted_submodule_paths, path); >> + } >> + } >> path_msg(opt, path, 0, >> _("CONFLICT (%s): Merge conflict in %s"), >> reason, path); >> diff --git a/merge-recursive.c b/merge-recursive.c >> index fd1bbde061..ff7cdbefe9 100644 >> --- a/merge-recursive.c >> +++ b/merge-recursive.c >> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, >> } >> >> if (!mfi->clean) { >> - if (S_ISGITLINK(mfi->blob.mode)) >> + if (S_ISGITLINK(mfi->blob.mode)) { >> reason = _("submodule"); >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { >> + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); >> + string_list_append(opt->conflicted_submodule_paths, path); >> + } >> + } >> output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), >> reason, path); >> if (ci && !df_conflict_remains) > > Nice that the changes needed to both the ort and recursive strategies > are so localized. :-) > >> diff --git a/merge-recursive.h b/merge-recursive.h >> index b88000e3c2..5d267e7a43 100644 >> --- a/merge-recursive.h >> +++ b/merge-recursive.h >> @@ -51,6 +51,10 @@ struct merge_options { >> >> /* internal fields used by the implementation */ >> struct merge_options_internal *priv; >> + >> + /* fields that hold submodule conflict information */ >> + struct oid_array *conflicted_submodule_oids; >> + struct string_list *conflicted_submodule_paths; >> }; > > Make sense. > >> void init_merge_options(struct merge_options *opt, struct repository *repo); >> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh >> index 178413c22f..5b384dedc1 100755 >> --- a/t/t6437-submodule-merge.sh >> +++ b/t/t6437-submodule-merge.sh >> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' >> test_must_fail git merge c 2> actual >> fi && >> grep $(cat expect) actual > /dev/null && >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && >> git reset --hard) >> ' >> In this test, an existing merge does exist, and is suggested to the user with [message 2] above. So telling users to do a new merge in the submodule is maybe to what we want. >> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' >> fi && >> grep $(cat expect1) actual > /dev/null && >> grep $(cat expect2) actual > /dev/null && >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && >> git reset --hard) >> ' Here, 2 existing merges exist, and they are presented to the users with [message 3] above. So again we might not want to tell users to do a new merge. >> >> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' >> git commit -a -m "f" && >> >> git checkout -b test-backward e && >> - test_must_fail git merge f) >> + test_must_fail git merge f >actual && >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) > > test_i18ngrep is apparently on the way out: > > $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh > # Wrapper for grep which used to be used for > # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other > # in-flight changes. Should not be used and will be removed soon. > test_i18ngrep () { > > I think you just want to use grep instead here for each of these hunks. > Here, one side regresses the submodule commit and the other side advances it. In this case, I really think the right resolution is to choose one side or the other, and not suggest to do a merge at all. So that means that we might want to tweak the advice we give based on the type of submodule conflict... Also, maybe we would want a new test that reproduces exactly the conditions of [message 1], i.e. no existing merge exists in the submodule. Thanks a lot for wanting to improve the submodule UX! Cheers, Philippe. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-11 17:08 ` Philippe Blain @ 2022-06-12 6:56 ` Elijah Newren 2022-06-13 2:03 ` Calvin Wan 1 sibling, 0 replies; 60+ messages in thread From: Elijah Newren @ 2022-06-12 6:56 UTC (permalink / raw) To: Philippe Blain; +Cc: Calvin Wan, Git Mailing List, Glen Choo, Junio C Hamano Hi Philippe, On Sat, Jun 11, 2022 at 10:08 AM Philippe Blain <levraiphilippeblain@gmail.com> wrote: > > Hi Calvin and Elijah, > > Le 2022-06-11 à 00:53, Elijah Newren a écrit : > > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote: > >> > >> When attempting to do a non-fast-forward merge in a project with > >> conflicts in the submodules, the merge fails and git prints the > >> following error: > > I think we would want to be slightly more precise here, as > "conflicts in the submodules" could be understood to mean: > > 1) conflicting submodule pointers in the superproject being merged > 2) 1. + also content conflicts in the submodule merge > > Here we are just talking about 1. Isn't that already addressed by Calvin's wording "recursive merging with submodules is currently not supported..."? Or are you concerned that this wording will be separated from the short conflict line? > Also, the merge does not automatically fail, it only fails if > a fast-forward *of the submodule pointer* is not possible, which > might be what you meant above; but to me "a non-fast-forward merge in a project > with conflict in the submodules" read like the non-fast-forwardness being talked about > was in the superproject, not in the submodule(s). > > [message 0] > >> > >> Failed to merge submodule <submodules> > >> CONFLICT (submodule):Merge conflict in <submodule> > >> Automatic merge failed; fix conflicts and then commit the result. > > Aside: the first <submodules> should be singular. > > This is indeed the output you get with the ort strategy if no existing merge commit > exist in the submodule repository that merges the submodule pointers recorded > in the superproject branches being merged. With the older "recursive" strategy, > this message is: > > [message 1] > Failed to merge submodule sub (merge following commits not found) > Auto-merging sub > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result > > c73cda76b1 (merge-ort: copy and adapt merge_submodule() > from merge-recursive.c, 2021-01-01) does not mention why that error message > was changed, but perhaps it is just because it is slightly confusing > to the user (they might not be expecting Git to look for an existing > merge and so they don't know what merge the message is talking about). The text in parentheses from merge-recursive.c didn't parse for me. Perhaps if it instead read "(failed to find merge commit in submodule to use as merge resolution)" or something like that then I might have kept it. As it was, I found it really hard to parse ("Why are there no commits after the instruction to 'merge following commits'"? Is "merge" a noun or a verb as used in the parenthetical comment? etc.). The text could have been improved, but a really easy way to improve it was just to remove it altogether. Removing it was an easy way to improve it, so I did that. Also, a little rant about merge_submodule()....that function originally had multiple suboptimal error messages, used to write directly to stdout instead of using the output() function to respect verbosity, didn't internationalize messages, didn't consider recursiveness in the resolution, has verbose output that only makes sense when updating the working tree and index, etc. It was quite a mess. Some of that got cleaned up in merge-recursive.c. Some only got cleaned up with the move to merge-ort.c. Some of it sadly still persists (as mentioned elsewhere in this email). > Maybe something like "failed to find existing commit merging <hash1> and <hash2>" > would be clearer... Yes, that would be much clearer. Can I tweak the suggestion to "failed to find an existing commit in submodule merging <hash1> and <hash2>"? However, while it's work in a related area (merging of submodules), I think this suggestion is separate from what Calvin is trying to fix and doesn't need to hold up his patch. > >> > >> Git is left in a conflicted state, which requires the user to: > >> 1. merge submodules > >> 2. add submodules changes to the superproject > >> 3. merge superproject > > > > I think we may need to tweak these steps a bit: > > > > 1. merge submodules OR update submodules to an already existing > > commit that reflects the merge (i.e. as submodules they may well be > > independently actively developed. Someone may have already merged the > > appropriate branch/commit and the already extant merges should be used > > in preference to creating new ones.) > > 2. <just as you said> > > 3. FINISH merging the superproject (i.e. don't redo the merge) > > > > I might be off on step 1; I have only used submodules extremely > > lightly and usually only for a limited time, so I'm not really sure > > what the expected workflow is. I could also imagine it potentially > > being repository-dependent whether you would want to merge or select > > an appropriate commit to update to. > > I agree with Elijah here, the submodule conflcit resolution might be to: > > 1) just choose one of the existing submodule commits on either side of > the superproject branches being merged > 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first) > 3) create such a merge commit (what you are talking about here) > > I also agree that it is highly repository- and workflow-dependent what > the "right" resolution is. > > Note that the code does try to find an existing merge commit in the submodule > repository, in this case the error message is different. If such a merge commit > exists: > > [message 2] > Failed to merge submodule sub, but a possible merge resolution exists: > aafcfa2 Merge branch 'sub-c' into sub-d > > > If this is correct simply add it to the index for example > by using: > > git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 "sub" > > which will accept this suggestion. > > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result. > > if multiple merge exist: > > [message 3] > Failed to merge submodule sub, but multiple possible merges exist: > 2729a0c Merge branch 'sub-c' into ambiguous > aafcfa2 Merge branch 'sub-c' into sub-d > > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result. > > Another aside, I really don't think we should instruct users to run > plumbing like 'git update-index --cacheinfo' , they should just cd into > the submodule and checkout the merge commit! +1 > > > >> These steps are non-obvious for newer submodule users to figure out > >> based on the error message and neither `git submodule status` nor `git > >> status` provide any useful pointers. > >> > >> Update error message to the following when attempting to do a > >> non-fast-forward merge in a project with conflicts in the submodules. > > > > Make sense. > > I agree that more guidance is a very nice addition. > > Regarding 'git status' output, it is downright confusing, since it says: > > Unmerged paths: > (use "git add <file>..." to mark resolution) > both modified: sub > > which is not at all what you want to do most of the time (that would > just stage whatever the currently checked out commit in the submodule is > at the moment!) Sure, but this isn't anything special about submodules. You don't want to "git add $path" if you haven't resolved the conflicts in $path first. That's advice that is true regardless of whether it's a regular text file that was present on both sides of the merge, in which case there will be conflict markers to remind the user to resolve conflicts first, or whether it's something else. And there are a lot of something elses without conflict markers: a submodule, a binary file, a symlink, path from file/directory conflict, something with conflicting modes, a file with any of a variety of rename-related path conflicts, maybe others. Perhaps you just want the advice changed to (use "git add <file>..." after resolving conflicts for the specified path(s)) ? It's a bit of a mouthful, but I guess it addresses your concern. It's another possible improvement that belongs outside Calvin's patch so it can be discussed separately. :-) >> The error message is based off of what would happen when `merge > >> --recurse-submodules` is eventually supported > >> > >> Failed to merge submodule <submodule> > >> CONFLICT (submodule): Merge conflict in <submodule> > >> Automatic merge failed; recursive merging with submodules is currently > >> not supported. To manually complete the merge: > >> - go to submodule (<submodule>), and merge commit <commit> > >> - come back to superproject, and `git add <submodule>` to record the above merge > >> - resolve any other conflicts in the superproject > >> - commit the resulting index in the superproject > > > > Ah, I see you've fixed step 3 here; that's good. > > > > However, these steps miss out on the merge-or-update submodule > > possibility...and since you mention these steps are potentially the > > basis for some future work, I think it's worth calling that out again. > > I'm slightly worried that the 'update' part of merge-or-update may > > throw a wrench in the plans for `merge --recurse-submodules`. > > > > Slightly off topic here, but for me the most important improvement that > 'git merge --recurse-submodules' would bring is when there is *no submodule > conflicts*, i.e. one side fast-forwards the submodule and the other side > does not touch it, since in that case the worktree of the submodule *is not updated* > by the current code, which is one of the most confusing aspect of using submodules > for new users ("why is "git status" and "git diff" not clean if "git merge" > was fast-forward ?!?"), and the same is true (maybe more even so) for 'git rebase'. Do you really mean "i.e." here or did you mean "e.g."? I think the example you give is just one where a submodule can be cleanly merged and then doesn't get updated. Another is when an already existing submodule merge commit can be used as the merge resolution for the submodule. The merging code simply doesn't update the working tree (or indexes) of submodules regardless of whether it updates the submodule pointers. Taking a quick glance, I think it might turn out to be trivial to fix this in merge-ort.c. I _think_ simply setting config_update_recurse_submodules = RECURSE_SUBMODULES_ON in merge-ort.c:checkout() anywhere before the call to unpack_trees() might be enough. Want to try it? The fix in merge-recursive is probably much more involved, and likely not worth the effort (there's a related comment in the code in update_file_flags() about this; look for "submodule"). > > (Also, continuing on the `merge --recurse-submodules` talent but > > discussing a different aspect of it, I'm curious if you need to add > > extra dirty-worktree/dirty-index checks for each submodule at the > > start of a merge, whether you need to try to lock N indexes before > > starting, and what other extra details are necessary. But those are > > probably questions to address whenever work on the future series to > > implement this option is underway.) > > > >> Changes since v1: > >> - Removed advice to abort merge > >> - Error message updated to contain more commit/submodule information > >> > >> Signed-off-by: Calvin Wan <calvinwan@google.com> > >> > >> --- > >> builtin/merge.c | 23 ++++++++++++++++++++++- > >> merge-ort.c | 7 ++++++- > >> merge-recursive.c | 7 ++++++- > >> merge-recursive.h | 4 ++++ > >> t/t6437-submodule-merge.sh | 5 ++++- > >> 5 files changed, 42 insertions(+), 4 deletions(-) > > > > So you're modifying the "git merge" porcelain level (builtin/merge.c), > > the two merges strategies, their common header, and adding some tests. > > No other porcelains are modified... > > > >> diff --git a/builtin/merge.c b/builtin/merge.c > >> index f178f5a3ee..7e2deea7fb 100644 > >> --- a/builtin/merge.c > >> +++ b/builtin/merge.c > >> @@ -88,6 +88,8 @@ static const char *sign_commit; > >> static int autostash; > >> static int no_verify; > >> static char *into_name; > >> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; > >> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; > >> > >> static struct strategy all_strategy[] = { > >> { "recursive", NO_TRIVIAL }, > >> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, > >> } > >> > >> init_merge_options(&o, the_repository); > >> + o.conflicted_submodule_oids = &conflicted_submodule_oids; > >> + o.conflicted_submodule_paths = &conflicted_submodule_paths; > >> if (!strcmp(strategy, "subtree")) > >> o.subtree_shift = ""; > >> > >> @@ -973,8 +977,25 @@ static int suggest_conflicts(void) > >> strbuf_release(&msgbuf); > >> fclose(fp); > >> repo_rerere(the_repository, allow_rerere_auto); > >> - printf(_("Automatic merge failed; " > >> + if (conflicted_submodule_oids.nr > 0) { > >> + int i; > >> + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" > >> + "not supported. To manually complete the merge:\n")); > >> + for (i = 0; i < conflicted_submodule_oids.nr; i++) { > >> + printf(_(" - go to submodule (%s), and merge commit %s\n"), > >> + conflicted_submodule_paths.items[i].string, > >> + oid_to_hex(&conflicted_submodule_oids.oid[i])); > >> + } > >> + printf(_(" - come back to superproject, and `git add")); > >> + for (i = 0; i < conflicted_submodule_paths.nr; i++) > >> + printf(_(" %s"), conflicted_submodule_paths.items[i].string); > >> + printf(_("` to record the above merge \n" > >> + " - resolve any other conflicts in the superproject\n" > >> + " - commit the resulting index in the superproject\n")); > >> + } else { > >> + printf(_("Automatic merge failed; " > >> "fix conflicts and then commit the result.\n")); > >> + } > >> return 1; > >> } > > > > This is kind of nice. I was worried you were going to embed these > > messages in the merge strategies, which could cause problems for other > > users of the merge strategies such as the --remerge-diff options to > > git log and git show (your new messages would be unwanted noise or > > even cause confusion there), and to the merge-tree work. In fact, a > > current submodule-merging message (search for "--cacheinfo") that is > > potentially similar to what you are adding here but which was added at > > the merge strategy level already feels highly problematic to me. I've > > been considering nuking it from the codebase for some time because of > > those issues, though I guess just moving it out elsewhere may also > > work. > > > > Yes, this is the message I copied above. I agree that if we can tweak this > advice to instead mention 'git checkout' and add it to the message > that Calvin is adding in this series, it would make for a really better > UX. This message shouldn't be tweaked; it should be expunged. It does not belong in the low-level code where it is. Now, if someone wants to move the message somewhere else so that it only is displayed when the working tree and index are updated, and they want to tweak it at the same time to avoid the ugly suggestion to use low-level plumbing, that'd be great. But neither of those things need to hold up Calvin's series. (I think you're providing lots of good points of what can be improved, Philippe, I just want to be clear that I think they are related but separate improvements.) > > However, this implementation does have a drawback: these messages > > won't appear for rebases, cherry-picks, reverts, attempted unstashing > > (git stash apply/pop), or other actions unless you update the relevant > > porcelains for those as well. > > > > A possible alternative here would be to move it to the level of > > merge-recursive and merge-ort that is only called when the working > > tree and index are updated. For example, placing it in > > merge_finalize() in merge-recursive.c and merge_switch_to_result() in > > merge-ort.c -- next to the diff_warn_rename_limit() call in each case. > > However, I'm also fine with keeping it at the porcelain level, it just > > may need to be in a function that is called from several porcelains > > that way. > > I think moving it to merge_finalize / merge_switch_to_result is indeed a > good suggestion, then we might be improving the UX across the board and not just > for 'git merge'. > > > > >> diff --git a/merge-ort.c b/merge-ort.c > >> index 0d3f42592f..c86ee11614 100644 > >> --- a/merge-ort.c > >> +++ b/merge-ort.c > >> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, > >> const char *reason = _("content"); > >> if (ci->filemask == 6) > >> reason = _("add/add"); > >> - if (S_ISGITLINK(merged_file.mode)) > >> + if (S_ISGITLINK(merged_file.mode)) { > >> reason = _("submodule"); > >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > >> + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); > >> + string_list_append(opt->conflicted_submodule_paths, path); > >> + } > >> + } > >> path_msg(opt, path, 0, > >> _("CONFLICT (%s): Merge conflict in %s"), > >> reason, path); > >> diff --git a/merge-recursive.c b/merge-recursive.c > >> index fd1bbde061..ff7cdbefe9 100644 > >> --- a/merge-recursive.c > >> +++ b/merge-recursive.c > >> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, > >> } > >> > >> if (!mfi->clean) { > >> - if (S_ISGITLINK(mfi->blob.mode)) > >> + if (S_ISGITLINK(mfi->blob.mode)) { > >> reason = _("submodule"); > >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > >> + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); > >> + string_list_append(opt->conflicted_submodule_paths, path); > >> + } > >> + } > >> output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), > >> reason, path); > >> if (ci && !df_conflict_remains) > > > > Nice that the changes needed to both the ort and recursive strategies > > are so localized. :-) > > > >> diff --git a/merge-recursive.h b/merge-recursive.h > >> index b88000e3c2..5d267e7a43 100644 > >> --- a/merge-recursive.h > >> +++ b/merge-recursive.h > >> @@ -51,6 +51,10 @@ struct merge_options { > >> > >> /* internal fields used by the implementation */ > >> struct merge_options_internal *priv; > >> + > >> + /* fields that hold submodule conflict information */ > >> + struct oid_array *conflicted_submodule_oids; > >> + struct string_list *conflicted_submodule_paths; > >> }; > > > > Make sense. > > > >> void init_merge_options(struct merge_options *opt, struct repository *repo); > >> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > >> index 178413c22f..5b384dedc1 100755 > >> --- a/t/t6437-submodule-merge.sh > >> +++ b/t/t6437-submodule-merge.sh > >> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' > >> test_must_fail git merge c 2> actual > >> fi && > >> grep $(cat expect) actual > /dev/null && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > >> git reset --hard) > >> ' > >> > > In this test, an existing merge does exist, and is suggested to the user with [message 2] above. > So telling users to do a new merge in the submodule is maybe to what we want. > > >> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > >> fi && > >> grep $(cat expect1) actual > /dev/null && > >> grep $(cat expect2) actual > /dev/null && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > >> git reset --hard) > >> ' > > Here, 2 existing merges exist, and they are presented to the users with [message 3] above. > So again we might not want to tell users to do a new merge. > > >> > >> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' > >> git commit -a -m "f" && > >> > >> git checkout -b test-backward e && > >> - test_must_fail git merge f) > >> + test_must_fail git merge f >actual && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) > > > > test_i18ngrep is apparently on the way out: > > > > $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh > > # Wrapper for grep which used to be used for > > # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other > > # in-flight changes. Should not be used and will be removed soon. > > test_i18ngrep () { > > > > I think you just want to use grep instead here for each of these hunks. > > > > Here, one side regresses the submodule commit and the other side advances it. > In this case, I really think the right resolution is to choose one side or > the other, and not suggest to do a merge at all. So that means that we might > want to tweak the advice we give based on the type of submodule conflict... > > Also, maybe we would want a new test that reproduces exactly the conditions of > [message 1], i.e. no existing merge exists in the submodule. > > Thanks a lot for wanting to improve the submodule UX! > > Cheers, > > Philippe. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-11 17:08 ` Philippe Blain 2022-06-12 6:56 ` Elijah Newren @ 2022-06-13 2:03 ` Calvin Wan 1 sibling, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-13 2:03 UTC (permalink / raw) To: Philippe Blain; +Cc: Elijah Newren, Git Mailing List, Glen Choo, Junio C Hamano > I think we would want to be slightly more precise here, as > "conflicts in the submodules" could be understood to mean: > 1) conflicting submodule pointers in the superproject being merged > 2) 1. + also content conflicts in the submodule merge > Here we are just talking about 1. Will clarify > Also, the merge does not automatically fail, it only fails if > a fast-forward *of the submodule pointer* is not possible, which > might be what you meant above; but to me "a non-fast-forward merge in a project > with conflict in the submodules" read like the non-fast-forwardness being talked about > was in the superproject, not in the submodule(s). It was what I meant above, but I do agree that I can reword this section as well. > I agree with Elijah here, the submodule conflcit resolution might be to: > 1) just choose one of the existing submodule commits on either side of > the superproject branches being merged > 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first) > 3) create such a merge commit (what you are talking about here) > I also agree that it is highly repository- and workflow-dependent what > the "right" resolution is. I replied to Elijah's message with some ideas I had about updating my first step, but to summarize I do agree that my current message doesn't account for the fact that a merge resolution may already exist, and the test cases you mention serve as proof that my current message is insufficient. > I think moving it to merge_finalize / merge_switch_to_result is indeed a > good suggestion, then we might be improving the UX across the board and not just > for 'git merge'. I'll spend some time looking into moving the suggestion code into these functions. My main concern is knowing which tests I would have to update to test these changes. On Sat, Jun 11, 2022 at 10:08 AM Philippe Blain <levraiphilippeblain@gmail.com> wrote: > > Hi Calvin and Elijah, > > Le 2022-06-11 à 00:53, Elijah Newren a écrit : > > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote: > >> > >> When attempting to do a non-fast-forward merge in a project with > >> conflicts in the submodules, the merge fails and git prints the > >> following error: > > I think we would want to be slightly more precise here, as > "conflicts in the submodules" could be understood to mean: > > 1) conflicting submodule pointers in the superproject being merged > 2) 1. + also content conflicts in the submodule merge > > Here we are just talking about 1. > > Also, the merge does not automatically fail, it only fails if > a fast-forward *of the submodule pointer* is not possible, which > might be what you meant above; but to me "a non-fast-forward merge in a project > with conflict in the submodules" read like the non-fast-forwardness being talked about > was in the superproject, not in the submodule(s). > > [message 0] > >> > >> Failed to merge submodule <submodules> > >> CONFLICT (submodule):Merge conflict in <submodule> > >> Automatic merge failed; fix conflicts and then commit the result. > > Aside: the first <submodules> should be singular. > > This is indeed the output you get with the ort strategy if no existing merge commit > exist in the submodule repository that merges the submodule pointers recorded > in the superproject branches being merged. With the older "recursive" strategy, > this message is: > > [message 1] > Failed to merge submodule sub (merge following commits not found) > Auto-merging sub > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result > > c73cda76b1 (merge-ort: copy and adapt merge_submodule() > from merge-recursive.c, 2021-01-01) does not mention why that error message > was changed, but perhaps it is just because it is slightly confusing > to the user (they might not be expecting Git to look for an existing > merge and so they don't know what merge the message is talking about). > > Maybe something like "failed to find existing commit merging <hash1> and <hash2>" > would be clearer... > > >> > >> Git is left in a conflicted state, which requires the user to: > >> 1. merge submodules > >> 2. add submodules changes to the superproject > >> 3. merge superproject > > > > I think we may need to tweak these steps a bit: > > > > 1. merge submodules OR update submodules to an already existing > > commit that reflects the merge (i.e. as submodules they may well be > > independently actively developed. Someone may have already merged the > > appropriate branch/commit and the already extant merges should be used > > in preference to creating new ones.) > > 2. <just as you said> > > 3. FINISH merging the superproject (i.e. don't redo the merge) > > > > I might be off on step 1; I have only used submodules extremely > > lightly and usually only for a limited time, so I'm not really sure > > what the expected workflow is. I could also imagine it potentially > > being repository-dependent whether you would want to merge or select > > an appropriate commit to update to. > > I agree with Elijah here, the submodule conflcit resolution might be to: > > 1) just choose one of the existing submodule commits on either side of > the superproject branches being merged > 2) choose an exisiting merge commit in the submodule repository (maybe after fetching it first) > 3) create such a merge commit (what you are talking about here) > > I also agree that it is highly repository- and workflow-dependent what > the "right" resolution is. > > Note that the code does try to find an existing merge commit in the submodule > repository, in this case the error message is different. If such a merge commit > exists: > > [message 2] > Failed to merge submodule sub, but a possible merge resolution exists: > aafcfa2 Merge branch 'sub-c' into sub-d > > > If this is correct simply add it to the index for example > by using: > > git update-index --cacheinfo 160000 aafcfa2a62764282ab848d5d6bea86ba217c1b24 "sub" > > which will accept this suggestion. > > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result. > > if multiple merge exist: > > [message 3] > Failed to merge submodule sub, but multiple possible merges exist: > 2729a0c Merge branch 'sub-c' into ambiguous > aafcfa2 Merge branch 'sub-c' into sub-d > > CONFLICT (submodule): Merge conflict in sub > Automatic merge failed; fix conflicts and then commit the result. > > Another aside, I really don't think we should instruct users to run > plumbing like 'git update-index --cacheinfo' , they should just cd into > the submodule and checkout the merge commit! > > > > >> These steps are non-obvious for newer submodule users to figure out > >> based on the error message and neither `git submodule status` nor `git > >> status` provide any useful pointers. > >> > >> Update error message to the following when attempting to do a > >> non-fast-forward merge in a project with conflicts in the submodules. > > > > Make sense. > > I agree that more guidance is a very nice addition. > > Regarding 'git status' output, it is downright confusing, since it says: > > Unmerged paths: > (use "git add <file>..." to mark resolution) > both modified: sub > > which is not at all what you want to do most of the time (that would > just stage whatever the currently checked out commit in the submodule is > at the moment!) > > > > >> The error message is based off of what would happen when `merge > >> --recurse-submodules` is eventually supported > >> > >> Failed to merge submodule <submodule> > >> CONFLICT (submodule): Merge conflict in <submodule> > >> Automatic merge failed; recursive merging with submodules is currently > >> not supported. To manually complete the merge: > >> - go to submodule (<submodule>), and merge commit <commit> > >> - come back to superproject, and `git add <submodule>` to record the above merge > >> - resolve any other conflicts in the superproject > >> - commit the resulting index in the superproject > > > > Ah, I see you've fixed step 3 here; that's good. > > > > However, these steps miss out on the merge-or-update submodule > > possibility...and since you mention these steps are potentially the > > basis for some future work, I think it's worth calling that out again. > > I'm slightly worried that the 'update' part of merge-or-update may > > throw a wrench in the plans for `merge --recurse-submodules`. > > > > Slightly off topic here, but for me the most important improvement that > 'git merge --recurse-submodules' would bring is when there is *no submodule > conflicts*, i.e. one side fast-forwards the submodule and the other side > does not touch it, since in that case the worktree of the submodule *is not updated* > by the current code, which is one of the most confusing aspect of using submodules > for new users ("why is "git status" and "git diff" not clean if "git merge" > was fast-forward ?!?"), and the same is true (maybe more even so) for 'git rebase'. > > > (Also, continuing on the `merge --recurse-submodules` talent but > > discussing a different aspect of it, I'm curious if you need to add > > extra dirty-worktree/dirty-index checks for each submodule at the > > start of a merge, whether you need to try to lock N indexes before > > starting, and what other extra details are necessary. But those are > > probably questions to address whenever work on the future series to > > implement this option is underway.) > > > >> Changes since v1: > >> - Removed advice to abort merge > >> - Error message updated to contain more commit/submodule information > >> > >> Signed-off-by: Calvin Wan <calvinwan@google.com> > >> > >> --- > >> builtin/merge.c | 23 ++++++++++++++++++++++- > >> merge-ort.c | 7 ++++++- > >> merge-recursive.c | 7 ++++++- > >> merge-recursive.h | 4 ++++ > >> t/t6437-submodule-merge.sh | 5 ++++- > >> 5 files changed, 42 insertions(+), 4 deletions(-) > > > > So you're modifying the "git merge" porcelain level (builtin/merge.c), > > the two merges strategies, their common header, and adding some tests. > > No other porcelains are modified... > > > >> diff --git a/builtin/merge.c b/builtin/merge.c > >> index f178f5a3ee..7e2deea7fb 100644 > >> --- a/builtin/merge.c > >> +++ b/builtin/merge.c > >> @@ -88,6 +88,8 @@ static const char *sign_commit; > >> static int autostash; > >> static int no_verify; > >> static char *into_name; > >> +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; > >> +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; > >> > >> static struct strategy all_strategy[] = { > >> { "recursive", NO_TRIVIAL }, > >> @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, > >> } > >> > >> init_merge_options(&o, the_repository); > >> + o.conflicted_submodule_oids = &conflicted_submodule_oids; > >> + o.conflicted_submodule_paths = &conflicted_submodule_paths; > >> if (!strcmp(strategy, "subtree")) > >> o.subtree_shift = ""; > >> > >> @@ -973,8 +977,25 @@ static int suggest_conflicts(void) > >> strbuf_release(&msgbuf); > >> fclose(fp); > >> repo_rerere(the_repository, allow_rerere_auto); > >> - printf(_("Automatic merge failed; " > >> + if (conflicted_submodule_oids.nr > 0) { > >> + int i; > >> + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" > >> + "not supported. To manually complete the merge:\n")); > >> + for (i = 0; i < conflicted_submodule_oids.nr; i++) { > >> + printf(_(" - go to submodule (%s), and merge commit %s\n"), > >> + conflicted_submodule_paths.items[i].string, > >> + oid_to_hex(&conflicted_submodule_oids.oid[i])); > >> + } > >> + printf(_(" - come back to superproject, and `git add")); > >> + for (i = 0; i < conflicted_submodule_paths.nr; i++) > >> + printf(_(" %s"), conflicted_submodule_paths.items[i].string); > >> + printf(_("` to record the above merge \n" > >> + " - resolve any other conflicts in the superproject\n" > >> + " - commit the resulting index in the superproject\n")); > >> + } else { > >> + printf(_("Automatic merge failed; " > >> "fix conflicts and then commit the result.\n")); > >> + } > >> return 1; > >> } > > > > This is kind of nice. I was worried you were going to embed these > > messages in the merge strategies, which could cause problems for other > > users of the merge strategies such as the --remerge-diff options to > > git log and git show (your new messages would be unwanted noise or > > even cause confusion there), and to the merge-tree work. In fact, a > > current submodule-merging message (search for "--cacheinfo") that is > > potentially similar to what you are adding here but which was added at > > the merge strategy level already feels highly problematic to me. I've > > been considering nuking it from the codebase for some time because of > > those issues, though I guess just moving it out elsewhere may also > > work. > > > > Yes, this is the message I copied above. I agree that if we can tweak this > advice to instead mention 'git checkout' and add it to the message > that Calvin is adding in this series, it would make for a really better > UX. > > > However, this implementation does have a drawback: these messages > > won't appear for rebases, cherry-picks, reverts, attempted unstashing > > (git stash apply/pop), or other actions unless you update the relevant > > porcelains for those as well. > > > > A possible alternative here would be to move it to the level of > > merge-recursive and merge-ort that is only called when the working > > tree and index are updated. For example, placing it in > > merge_finalize() in merge-recursive.c and merge_switch_to_result() in > > merge-ort.c -- next to the diff_warn_rename_limit() call in each case. > > However, I'm also fine with keeping it at the porcelain level, it just > > may need to be in a function that is called from several porcelains > > that way. > > I think moving it to merge_finalize / merge_switch_to_result is indeed a > good suggestion, then we might be improving the UX across the board and not just > for 'git merge'. > > > > >> diff --git a/merge-ort.c b/merge-ort.c > >> index 0d3f42592f..c86ee11614 100644 > >> --- a/merge-ort.c > >> +++ b/merge-ort.c > >> @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, > >> const char *reason = _("content"); > >> if (ci->filemask == 6) > >> reason = _("add/add"); > >> - if (S_ISGITLINK(merged_file.mode)) > >> + if (S_ISGITLINK(merged_file.mode)) { > >> reason = _("submodule"); > >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > >> + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); > >> + string_list_append(opt->conflicted_submodule_paths, path); > >> + } > >> + } > >> path_msg(opt, path, 0, > >> _("CONFLICT (%s): Merge conflict in %s"), > >> reason, path); > >> diff --git a/merge-recursive.c b/merge-recursive.c > >> index fd1bbde061..ff7cdbefe9 100644 > >> --- a/merge-recursive.c > >> +++ b/merge-recursive.c > >> @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, > >> } > >> > >> if (!mfi->clean) { > >> - if (S_ISGITLINK(mfi->blob.mode)) > >> + if (S_ISGITLINK(mfi->blob.mode)) { > >> reason = _("submodule"); > >> + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > >> + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); > >> + string_list_append(opt->conflicted_submodule_paths, path); > >> + } > >> + } > >> output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), > >> reason, path); > >> if (ci && !df_conflict_remains) > > > > Nice that the changes needed to both the ort and recursive strategies > > are so localized. :-) > > > >> diff --git a/merge-recursive.h b/merge-recursive.h > >> index b88000e3c2..5d267e7a43 100644 > >> --- a/merge-recursive.h > >> +++ b/merge-recursive.h > >> @@ -51,6 +51,10 @@ struct merge_options { > >> > >> /* internal fields used by the implementation */ > >> struct merge_options_internal *priv; > >> + > >> + /* fields that hold submodule conflict information */ > >> + struct oid_array *conflicted_submodule_oids; > >> + struct string_list *conflicted_submodule_paths; > >> }; > > > > Make sense. > > > >> void init_merge_options(struct merge_options *opt, struct repository *repo); > >> diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > >> index 178413c22f..5b384dedc1 100755 > >> --- a/t/t6437-submodule-merge.sh > >> +++ b/t/t6437-submodule-merge.sh > >> @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' > >> test_must_fail git merge c 2> actual > >> fi && > >> grep $(cat expect) actual > /dev/null && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > >> git reset --hard) > >> ' > >> > > In this test, an existing merge does exist, and is suggested to the user with [message 2] above. > So telling users to do a new merge in the submodule is maybe to what we want. > > >> @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > >> fi && > >> grep $(cat expect1) actual > /dev/null && > >> grep $(cat expect2) actual > /dev/null && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > >> git reset --hard) > >> ' > > Here, 2 existing merges exist, and they are presented to the users with [message 3] above. > So again we might not want to tell users to do a new merge. > > >> > >> @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' > >> git commit -a -m "f" && > >> > >> git checkout -b test-backward e && > >> - test_must_fail git merge f) > >> + test_must_fail git merge f >actual && > >> + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) > > > > test_i18ngrep is apparently on the way out: > > > > $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh > > # Wrapper for grep which used to be used for > > # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other > > # in-flight changes. Should not be used and will be removed soon. > > test_i18ngrep () { > > > > I think you just want to use grep instead here for each of these hunks. > > > > Here, one side regresses the submodule commit and the other side advances it. > In this case, I really think the right resolution is to choose one side or > the other, and not suggest to do a merge at all. So that means that we might > want to tweak the advice we give based on the type of submodule conflict... > > Also, maybe we would want a new test that reproduces exactly the conditions of > [message 1], i.e. no existing merge exists in the submodule. > > Thanks a lot for wanting to improve the submodule UX! > > Cheers, > > Philippe. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-11 4:53 ` Elijah Newren 2022-06-11 17:08 ` Philippe Blain @ 2022-06-12 23:30 ` Junio C Hamano 2022-06-13 1:54 ` Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-06-12 23:30 UTC (permalink / raw) To: Elijah Newren; +Cc: Calvin Wan, Git Mailing List, Glen Choo Elijah Newren <newren@gmail.com> writes: >> Git is left in a conflicted state, which requires the user to: >> 1. merge submodules >> 2. add submodules changes to the superproject >> 3. merge superproject > > I think we may need to tweak these steps a bit: > > 1. merge submodules OR update submodules to an already existing > commit that reflects the merge (i.e. as submodules they may well be > independently actively developed. Someone may have already merged the > appropriate branch/commit and the already extant merges should be used > in preference to creating new ones.) That is a very good point. I suspect we should encourage users to find an existing merge, and possibly even discourage them from creating a new one on their own. There may not be much point in creating the same merge of identical parent commits with the same result that only has different metadata. It may be tempting to argue that an existing merge in a submodule and the merge you are creating now are made in different context because the superproject merges that necessitate thse submodule merges are different, but I doubt that is a healthy argument. A commit in the submodule should be able to explain its reason to exist on its own---after all, the superproject may know which submodules are used for what purpose, but a submodule does not have to know where it is used, and that is how it is made more reusable. > 2. <just as you said> > 3. FINISH merging the superproject (i.e. don't redo the merge) Yes, exactly. FWIW, an earlier draft was telling users to abort the merge in the first step, and its review corrected it. 1 and 2 are to be done in the context of the interrupted superproject merge, so its logical consequence is to conclude the merge at step 3. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v2] submodule merge: update conflict error message 2022-06-11 4:53 ` Elijah Newren 2022-06-11 17:08 ` Philippe Blain 2022-06-12 23:30 ` Junio C Hamano @ 2022-06-13 1:54 ` Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-13 1:54 UTC (permalink / raw) To: Elijah Newren; +Cc: Git Mailing List, Glen Choo, Junio C Hamano > I think we may need to tweak these steps a bit: > 1. merge submodules OR update submodules to an already existing > commit that reflects the merge (i.e. as submodules they may well be > independently actively developed. Someone may have already merged the > appropriate branch/commit and the already extant merges should be used > in preference to creating new ones.) > 2. <just as you said> > 3. FINISH merging the superproject (i.e. don't redo the merge) > I might be off on step 1; I have only used submodules extremely > lightly and usually only for a limited time, so I'm not really sure > what the expected workflow is. I could also imagine it potentially > being repository-dependent whether you would want to merge or select > an appropriate commit to update to. You are correct that either merging or selecting an appropriate commit to update to (if it exists) are the two ways to fix a submodule conflict. As Phillippe mentions below, if merge detects a possible merge resolution, that merge resolution is printed out as in the suggestion. I wonder if it's easy to pull out the exact reason for why the submodule merge failed. If that is the case, then I think changing the first step to something like: (If no merge resolution already exists, the suggestion stays the same) 1. - go to submodule (<submodule>) and merge commit <commit> (If a merge resolution exists) 1. - go to submodule (<submodule>). Either update the submodule to one of the possible merge commits or merge commit <commit> If it is not easy to pull out the merge fail reason, then I think we can generalize the first step to: 1. - go to submodule (<submodule>). Merge commit <commit> or if a possible merge resolution exists, then you have the option to update the submodule to one of the merge commits > However, this implementation does have a drawback: these messages > won't appear for rebases, cherry-picks, reverts, attempted unstashing > (git stash apply/pop), or other actions unless you update the relevant > porcelains for those as well. > A possible alternative here would be to move it to the level of > merge-recursive and merge-ort that is only called when the working > tree and index are updated. For example, placing it in > merge_finalize() in merge-recursive.c and merge_switch_to_result() in > merge-ort.c -- next to the diff_warn_rename_limit() call in each case. > However, I'm also fine with keeping it at the porcelain level, it just > may need to be in a function that is called from several porcelains > that way. You make a good point here. I'm inclined to keep it at the porcelain level since this is a temporary holdover until `merge --recurse-submodules` is implemented. > test_i18ngrep is apparently on the way out: ack On Fri, Jun 10, 2022 at 9:53 PM Elijah Newren <newren@gmail.com> wrote: > > On Fri, Jun 10, 2022 at 4:29 PM Calvin Wan <calvinwan@google.com> wrote: > > > > When attempting to do a non-fast-forward merge in a project with > > conflicts in the submodules, the merge fails and git prints the > > following error: > > > > Failed to merge submodule <submodules> > > CONFLICT (submodule):Merge conflict in <submodule> > > Automatic merge failed; fix conflicts and then commit the result. > > > > Git is left in a conflicted state, which requires the user to: > > 1. merge submodules > > 2. add submodules changes to the superproject > > 3. merge superproject > > I think we may need to tweak these steps a bit: > > 1. merge submodules OR update submodules to an already existing > commit that reflects the merge (i.e. as submodules they may well be > independently actively developed. Someone may have already merged the > appropriate branch/commit and the already extant merges should be used > in preference to creating new ones.) > 2. <just as you said> > 3. FINISH merging the superproject (i.e. don't redo the merge) > > I might be off on step 1; I have only used submodules extremely > lightly and usually only for a limited time, so I'm not really sure > what the expected workflow is. I could also imagine it potentially > being repository-dependent whether you would want to merge or select > an appropriate commit to update to. > > > These steps are non-obvious for newer submodule users to figure out > > based on the error message and neither `git submodule status` nor `git > > status` provide any useful pointers. > > > > Update error message to the following when attempting to do a > > non-fast-forward merge in a project with conflicts in the submodules. > > Make sense. > > > The error message is based off of what would happen when `merge > > --recurse-submodules` is eventually supported > > > > Failed to merge submodule <submodule> > > CONFLICT (submodule): Merge conflict in <submodule> > > Automatic merge failed; recursive merging with submodules is currently > > not supported. To manually complete the merge: > > - go to submodule (<submodule>), and merge commit <commit> > > - come back to superproject, and `git add <submodule>` to record the above merge > > - resolve any other conflicts in the superproject > > - commit the resulting index in the superproject > > Ah, I see you've fixed step 3 here; that's good. > > However, these steps miss out on the merge-or-update submodule > possibility...and since you mention these steps are potentially the > basis for some future work, I think it's worth calling that out again. > I'm slightly worried that the 'update' part of merge-or-update may > throw a wrench in the plans for `merge --recurse-submodules`. > > (Also, continuing on the `merge --recurse-submodules` talent but > discussing a different aspect of it, I'm curious if you need to add > extra dirty-worktree/dirty-index checks for each submodule at the > start of a merge, whether you need to try to lock N indexes before > starting, and what other extra details are necessary. But those are > probably questions to address whenever work on the future series to > implement this option is underway.) > > > Changes since v1: > > - Removed advice to abort merge > > - Error message updated to contain more commit/submodule information > > > > Signed-off-by: Calvin Wan <calvinwan@google.com> > > > > --- > > builtin/merge.c | 23 ++++++++++++++++++++++- > > merge-ort.c | 7 ++++++- > > merge-recursive.c | 7 ++++++- > > merge-recursive.h | 4 ++++ > > t/t6437-submodule-merge.sh | 5 ++++- > > 5 files changed, 42 insertions(+), 4 deletions(-) > > So you're modifying the "git merge" porcelain level (builtin/merge.c), > the two merges strategies, their common header, and adding some tests. > No other porcelains are modified... > > > diff --git a/builtin/merge.c b/builtin/merge.c > > index f178f5a3ee..7e2deea7fb 100644 > > --- a/builtin/merge.c > > +++ b/builtin/merge.c > > @@ -88,6 +88,8 @@ static const char *sign_commit; > > static int autostash; > > static int no_verify; > > static char *into_name; > > +static struct oid_array conflicted_submodule_oids = OID_ARRAY_INIT; > > +static struct string_list conflicted_submodule_paths = STRING_LIST_INIT_DUP; > > > > static struct strategy all_strategy[] = { > > { "recursive", NO_TRIVIAL }, > > @@ -734,6 +736,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, > > } > > > > init_merge_options(&o, the_repository); > > + o.conflicted_submodule_oids = &conflicted_submodule_oids; > > + o.conflicted_submodule_paths = &conflicted_submodule_paths; > > if (!strcmp(strategy, "subtree")) > > o.subtree_shift = ""; > > > > @@ -973,8 +977,25 @@ static int suggest_conflicts(void) > > strbuf_release(&msgbuf); > > fclose(fp); > > repo_rerere(the_repository, allow_rerere_auto); > > - printf(_("Automatic merge failed; " > > + if (conflicted_submodule_oids.nr > 0) { > > + int i; > > + printf(_("Automatic merge failed; recursive merging with submodules is currently\n" > > + "not supported. To manually complete the merge:\n")); > > + for (i = 0; i < conflicted_submodule_oids.nr; i++) { > > + printf(_(" - go to submodule (%s), and merge commit %s\n"), > > + conflicted_submodule_paths.items[i].string, > > + oid_to_hex(&conflicted_submodule_oids.oid[i])); > > + } > > + printf(_(" - come back to superproject, and `git add")); > > + for (i = 0; i < conflicted_submodule_paths.nr; i++) > > + printf(_(" %s"), conflicted_submodule_paths.items[i].string); > > + printf(_("` to record the above merge \n" > > + " - resolve any other conflicts in the superproject\n" > > + " - commit the resulting index in the superproject\n")); > > + } else { > > + printf(_("Automatic merge failed; " > > "fix conflicts and then commit the result.\n")); > > + } > > return 1; > > } > > This is kind of nice. I was worried you were going to embed these > messages in the merge strategies, which could cause problems for other > users of the merge strategies such as the --remerge-diff options to > git log and git show (your new messages would be unwanted noise or > even cause confusion there), and to the merge-tree work. In fact, a > current submodule-merging message (search for "--cacheinfo") that is > potentially similar to what you are adding here but which was added at > the merge strategy level already feels highly problematic to me. I've > been considering nuking it from the codebase for some time because of > those issues, though I guess just moving it out elsewhere may also > work. > > However, this implementation does have a drawback: these messages > won't appear for rebases, cherry-picks, reverts, attempted unstashing > (git stash apply/pop), or other actions unless you update the relevant > porcelains for those as well. > > A possible alternative here would be to move it to the level of > merge-recursive and merge-ort that is only called when the working > tree and index are updated. For example, placing it in > merge_finalize() in merge-recursive.c and merge_switch_to_result() in > merge-ort.c -- next to the diff_warn_rename_limit() call in each case. > However, I'm also fine with keeping it at the porcelain level, it just > may need to be in a function that is called from several porcelains > that way. > > > diff --git a/merge-ort.c b/merge-ort.c > > index 0d3f42592f..c86ee11614 100644 > > --- a/merge-ort.c > > +++ b/merge-ort.c > > @@ -3866,8 +3866,13 @@ static void process_entry(struct merge_options *opt, > > const char *reason = _("content"); > > if (ci->filemask == 6) > > reason = _("add/add"); > > - if (S_ISGITLINK(merged_file.mode)) > > + if (S_ISGITLINK(merged_file.mode)) { > > reason = _("submodule"); > > + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > > + oid_array_append(opt->conflicted_submodule_oids, &merged_file.oid); > > + string_list_append(opt->conflicted_submodule_paths, path); > > + } > > + } > > path_msg(opt, path, 0, > > _("CONFLICT (%s): Merge conflict in %s"), > > reason, path); > > diff --git a/merge-recursive.c b/merge-recursive.c > > index fd1bbde061..ff7cdbefe9 100644 > > --- a/merge-recursive.c > > +++ b/merge-recursive.c > > @@ -3149,8 +3149,13 @@ static int handle_content_merge(struct merge_file_info *mfi, > > } > > > > if (!mfi->clean) { > > - if (S_ISGITLINK(mfi->blob.mode)) > > + if (S_ISGITLINK(mfi->blob.mode)) { > > reason = _("submodule"); > > + if (opt->conflicted_submodule_oids && opt->conflicted_submodule_paths) { > > + oid_array_append(opt->conflicted_submodule_oids, &mfi->blob.oid); > > + string_list_append(opt->conflicted_submodule_paths, path); > > + } > > + } > > output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), > > reason, path); > > if (ci && !df_conflict_remains) > > Nice that the changes needed to both the ort and recursive strategies > are so localized. :-) > > > diff --git a/merge-recursive.h b/merge-recursive.h > > index b88000e3c2..5d267e7a43 100644 > > --- a/merge-recursive.h > > +++ b/merge-recursive.h > > @@ -51,6 +51,10 @@ struct merge_options { > > > > /* internal fields used by the implementation */ > > struct merge_options_internal *priv; > > + > > + /* fields that hold submodule conflict information */ > > + struct oid_array *conflicted_submodule_oids; > > + struct string_list *conflicted_submodule_paths; > > }; > > Make sense. > > > void init_merge_options(struct merge_options *opt, struct repository *repo); > > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > > index 178413c22f..5b384dedc1 100755 > > --- a/t/t6437-submodule-merge.sh > > +++ b/t/t6437-submodule-merge.sh > > @@ -141,6 +141,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' > > test_must_fail git merge c 2> actual > > fi && > > grep $(cat expect) actual > /dev/null && > > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > > git reset --hard) > > ' > > > > @@ -167,6 +168,7 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > > fi && > > grep $(cat expect1) actual > /dev/null && > > grep $(cat expect2) actual > /dev/null && > > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-b)" actual && > > git reset --hard) > > ' > > > > @@ -205,7 +207,8 @@ test_expect_success 'merging should fail for changes that are backwards' ' > > git commit -a -m "f" && > > > > git checkout -b test-backward e && > > - test_must_fail git merge f) > > + test_must_fail git merge f >actual && > > + test_i18ngrep "go to submodule (sub), and merge commit $(git -C sub rev-parse sub-a)" actual) > > test_i18ngrep is apparently on the way out: > > $ grep -B 3 ^test_i18ngrep t/test-lib-functions.sh > # Wrapper for grep which used to be used for > # GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other > # in-flight changes. Should not be used and will be removed soon. > test_i18ngrep () { > > I think you just want to use grep instead here for each of these hunks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v3] submodule merge: update conflict error message 2022-06-10 23:11 ` [PATCH v2] " Calvin Wan 2022-06-11 4:53 ` Elijah Newren @ 2022-06-29 22:40 ` Calvin Wan 2022-06-30 2:40 ` Elijah Newren ` (2 more replies) 1 sibling, 3 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-29 22:40 UTC (permalink / raw) To: git; +Cc: chooglen, gitster, newren, levraiphilippeblain, Calvin Wan When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded, the merge fails and git prints the following error: Failed to merge submodule <submodule> CONFLICT (submodule):Merge conflict in <submodule> Automatic merge failed; fix conflicts and then commit the result. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to match the steps above. If git does not detect a merge resolution, the following is printed: -------- Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; recursive merging with submodules is currently not supported. To manually complete the merge: - go to submodule (<submodule>), and merge commit <commit> - come back to superproject, and `git add <submodule>` to record the above merge - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. -------- If git detects a possible merge resolution, the following is printed: -------- Failed to merge submodule sub, but a possible merge resolution exists: <commit> Merge branch '<branch1>' into <branch2> If this is correct simply add it to the index for example by using: git update-index --cacheinfo 160000 <commit> "<submodule>" which will accept this suggestion. CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules is currently not supported. To manually complete the merge: - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> - come back to superproject, and `git add <submodule>` to record the above merge - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. -------- If git detects multiple possible merge resolutions, the following is printed: -------- Failed to merge submodule sub, but multiple possible merges exist: <commit> Merge branch '<branch1>' into <branch2> <commit> Merge branch '<branch1>' into <branch3> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules is currently not supported. To manually complete the merge: - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> - come back to superproject, and `git add <submodule>` to record the above merge - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ------- Changes since v2: There are three major changes in this patch thanks to all the helpful feedback! I have moved the print function from builtin/merge.c to merge-ort.c and added it to merge_finalize() in merge-recursive.c and merge_switch_to_result() in merge-ort.c. This allows other merge machinery commands besides merge to print submodule conflict advice. I have moved the check for submodule conflicts from process_entry() to merge_submodule(). This also required removing the early returns and instead going to my submodule conflict check allowing us to pass back information on whether git detected a possible merge resolution or not. I have also updated the error message to better reflect the merge status. Specifically, if git detects a possible merge resolution, the error message now also suggest updating to one of those resolutions. Other small changes: - Moved fields that hold submodule conflict information to new object - Shortened printed commit id to DEFAULT_ABBREV - Added a test to t6437-submodule-merge.sh for merges with no resolution existence - Modified a test in t7402-submodule-rebase to show error message prints in other parts of the merge machinery Changes since v1: - Removed advice to abort merge - Error message updated to contain more commit/submodule information Signed-off-by: Calvin Wan <calvinwan@google.com> --- merge-ort.c | 53 ++++++++++++++++++++++++++++++++++--- merge-recursive.c | 26 +++++++++++++++--- merge-recursive.h | 18 +++++++++++++ t/t6437-submodule-merge.sh | 36 ++++++++++++++++++++++--- t/t7402-submodule-rebase.sh | 6 +++-- 5 files changed, 125 insertions(+), 14 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 0d3f42592f..2d9f03ea8c 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -1610,6 +1610,9 @@ static int merge_submodule(struct merge_options *opt, struct commit *commit_o, *commit_a, *commit_b; int parent_count; struct object_array merges; + struct conflicted_submodule_list *csub = opt->conflicted_submodules; + struct conflicted_submodule_item csub_item; + int resolution_exists = 0; int i; int search = !opt->priv->call_depth; @@ -1619,17 +1622,17 @@ static int merge_submodule(struct merge_options *opt, /* we can not handle deletion conflicts */ if (is_null_oid(o)) - return 0; + goto ret; if (is_null_oid(a)) - return 0; + goto ret; if (is_null_oid(b)) - return 0; + goto ret; if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { path_msg(opt, path, 0, _("Failed to merge submodule %s (not checked out)"), path); - return 0; + goto ret; } if (!(commit_o = lookup_commit_reference(&subrepo, o)) || @@ -1703,6 +1706,7 @@ static int merge_submodule(struct merge_options *opt, "which will accept this suggestion.\n"), oid_to_hex(&merges.objects[0].item->oid), path); strbuf_release(&sb); + resolution_exists = 1; break; default: for (i = 0; i < merges.nr; i++) @@ -1712,11 +1716,24 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s, but multiple " "possible merges exist:\n%s"), path, sb.buf); strbuf_release(&sb); + resolution_exists = 1; } object_array_clear(&merges); cleanup: repo_clear(&subrepo); +ret: + if (!ret) { + if (!csub) { + CALLOC_ARRAY(csub, 1); + } + csub_item.oid = *result; + csub_item.path = xstrdup(path); + csub_item.resolution_exists = resolution_exists; + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); + csub->items[csub->nr++] = csub_item; + opt->conflicted_submodules = csub; + } return ret; } @@ -4256,6 +4273,32 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { + if (csub && csub->nr > 0) { + int i; + printf(_("Recursive merging with submodules is currently not supported.\n" + "To manually complete the merge:\n")); + for (i = 0; i < csub->nr; i++) { + const struct object_id id = csub->items[i].oid; + if (csub->items[i].resolution_exists) + printf(_(" - go to submodule (%s), and either update the submodule " + "to a possible commit above or merge commit %s\n"), + csub->items[i].path, + find_unique_abbrev(&id, DEFAULT_ABBREV)); + else + printf(_(" - go to submodule (%s), and merge commit %s\n"), + csub->items[i].path, + find_unique_abbrev(&id, DEFAULT_ABBREV)); + } + printf(_(" - come back to superproject, and `git add")); + for (i = 0; i < csub->nr; i++) + printf(_(" %s"), csub->items[i].path); + printf(_("` to record the above merge \n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n")); + } +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result, @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(opt->conflicted_submodules); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); diff --git a/merge-recursive.c b/merge-recursive.c index fd1bbde061..311cc37886 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1190,6 +1190,9 @@ static int merge_submodule(struct merge_options *opt, struct commit *commit_base, *commit_a, *commit_b; int parent_count; struct object_array merges; + struct conflicted_submodule_list *csub = opt->conflicted_submodules; + struct conflicted_submodule_item csub_item; + int resolution_exists = 0; int i; int search = !opt->priv->call_depth; @@ -1204,15 +1207,15 @@ static int merge_submodule(struct merge_options *opt, /* we can not handle deletion conflicts */ if (is_null_oid(base)) - return 0; + goto ret; if (is_null_oid(a)) - return 0; + goto ret; if (is_null_oid(b)) - return 0; + goto ret; if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path); - return 0; + goto ret; } if (!(commit_base = lookup_commit_reference(&subrepo, base)) || @@ -1287,17 +1290,31 @@ static int merge_submodule(struct merge_options *opt, " git update-index --cacheinfo 160000 %s \"%s\"\n\n" "which will accept this suggestion.\n"), oid_to_hex(&merges.objects[0].item->oid), path); + resolution_exists = 1; break; default: output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path); for (i = 0; i < merges.nr; i++) print_commit(&subrepo, (struct commit *) merges.objects[i].item); + resolution_exists = 1; } object_array_clear(&merges); cleanup: repo_clear(&subrepo); +ret: + if (!ret) { + if (!csub) { + CALLOC_ARRAY(csub, 1); + } + csub_item.oid = *result; + csub_item.path = xstrdup(path); + csub_item.resolution_exists = resolution_exists; + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); + csub->items[csub->nr++] = csub_item; + opt->conflicted_submodules = csub; + } return ret; } @@ -3736,6 +3753,7 @@ static void merge_finalize(struct merge_options *opt) flush_output(opt); if (!opt->priv->call_depth && opt->buffer_output < 2) strbuf_release(&opt->obuf); + print_submodule_conflict_suggestion(opt->conflicted_submodules); if (show(opt, 2)) diff_warn_rename_limit("merge.renamelimit", opt->priv->needed_rename_limit, 0); diff --git a/merge-recursive.h b/merge-recursive.h index b88000e3c2..b615955fb7 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -9,6 +9,7 @@ struct object_id; struct repository; struct tree; +struct conflicted_submodule_list; struct merge_options_internal; struct merge_options { struct repository *repo; @@ -51,6 +52,21 @@ struct merge_options { /* internal fields used by the implementation */ struct merge_options_internal *priv; + + /* field that holds submodule conflict information */ + struct conflicted_submodule_list *conflicted_submodules; +}; + +struct conflicted_submodule_item { + struct object_id oid; + char *path; + int resolution_exists; +}; + +struct conflicted_submodule_list { + struct conflicted_submodule_item *items; + size_t nr; + size_t alloc; }; void init_merge_options(struct merge_options *opt, struct repository *repo); @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt, const struct object_id **merge_bases, struct commit **result); +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub); + #endif diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 178413c22f..f0a31c3a61 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,28 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' + +test_expect_success 'merging should conflict for non fast-forward' ' + (cd merge-search && + git checkout -b test-nonforward-a b && + (cd sub && + git rev-parse --short sub-b > ../expect) && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual + else + test_must_fail git merge c 2> actual + fi && + grep $(cat expect) actual > /dev/null && + sub_expect="go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-b)" && + grep "$sub_expect" actual && + git reset --hard) +' +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,9 +149,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort @@ -141,6 +161,9 @@ test_expect_success 'merging should conflict for non fast-forward' ' test_must_fail git merge c 2> actual fi && grep $(cat expect) actual > /dev/null && + sub_expect="go to submodule (sub), and either update the submodule to a \ +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)" && + grep "$sub_expect" actual && git reset --hard) ' @@ -167,6 +190,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' fi && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && + sub_expect="go to submodule (sub), and either update the submodule to a \ +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)" && + grep "$sub_expect" actual && git reset --hard) ' @@ -205,7 +231,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + sub_expect="go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-a)" && + grep "$sub_expect" actual) ' diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..21c8b4e97c 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + sub_expect="go to submodule (submodule), and merge commit $(git -C submodule rev-parse --short HEAD^^)" && + grep "$sub_expect" actual_output ' test_done base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 -- 2.37.0.rc0.161.g10f37bed90-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan @ 2022-06-30 2:40 ` Elijah Newren 2022-06-30 19:48 ` Calvin Wan 2022-06-30 20:35 ` Glen Choo 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan 2 siblings, 1 reply; 60+ messages in thread From: Elijah Newren @ 2022-06-30 2:40 UTC (permalink / raw) To: Calvin Wan; +Cc: Git Mailing List, Glen Choo, Junio C Hamano, Philippe Blain Hi, On Wed, Jun 29, 2022 at 3:41 PM Calvin Wan <calvinwan@google.com> wrote: > > When attempting to merge in a superproject with conflicting submodule > pointers that cannot be fast-forwarded, the merge fails and git prints > the following error: > > Failed to merge submodule <submodule> > CONFLICT (submodule):Merge conflict in <submodule> > Automatic merge failed; fix conflicts and then commit the result. > > Git is left in a conflicted state, which requires the user to: > 1. merge submodules or update submodules to an already existing > commit that reflects the merge > 2. add submodules changes to the superproject > 3. finish merging superproject > These steps are non-obvious for newer submodule users to figure out > based on the error message and neither `git submodule status` nor `git > status` provide any useful pointers. > > Update error message to match the steps above. If git does not detect a > merge resolution, the following is printed: > > -------- > > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Automatic merge failed; recursive merging with submodules is currently > not supported. To manually complete the merge: > - go to submodule (<submodule>), and merge commit <commit> I'm still a little unsure on this. The merge_submodule() logic we have may not have detected a merge commit that merges the two branches together, but that doesn't automatically imply a new merge must be created in the submodule to resolve this conflict. There might be various reasons that other existing commits in the submodule could be used as the "correct" update. Perhaps that is uncommon enough to not be worth mentioning; I'm just a little hesitant to treat the merge_submodule() logic as robust and finding the only choices users might want to use. If we do keep the wording as-is, it might be nice to at least discuss in the commit message why we chose that and ignored the or-update-submodule option in this case. > - come back to superproject, and `git add <submodule>` to record the above merge > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > -------- > > If git detects a possible merge resolution, the following is printed: > > -------- > > Failed to merge submodule sub, but a possible merge resolution exists: > <commit> Merge branch '<branch1>' into <branch2> > > > If this is correct simply add it to the index for example > by using: > > git update-index --cacheinfo 160000 <commit> "<submodule>" > > which will accept this suggestion. The last few lines above will no longer be part of the output once en/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove command-line-centric submodule message from merge-ort, 2022-06-18). > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules is currently not supported. > To manually complete the merge: > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> Again, I'm hesitant to treat the suggestions from merge_submodule() as the only possibilities. For example, perhaps the user will want to pick a commit that contains one of the ones found by merge_submodule() in its history -- perhaps because the newer commit they want to select contains an important bugfix for an issue not discovered at the time of the merge in the submodule. Also, I'm worried your sentence may be easy to misunderstand, due to it being ambiguous whether "merge" is a verb or an adjective. More precisely, your sentence could be read as "update the submodule to a possible commit above, or update the submodule to merge commit <commit>" and then users say, "But <commit> isn't a merge commit; why does this message claim it is? Do I just update the submodule to that commit anyway?" Or perhaps users just update it to <commit> without even checking to find out that it's not a merge commit, with the deleterious consequences of missing all the important changes currently found in the submodule. > - come back to superproject, and `git add <submodule>` to record the above merge "...to record the above merge or update"? > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > -------- > > If git detects multiple possible merge resolutions, the following is printed: > > -------- > > Failed to merge submodule sub, but multiple possible merges exist: > <commit> Merge branch '<branch1>' into <branch2> > <commit> Merge branch '<branch1>' into <branch3> > > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules is currently not supported. > To manually complete the merge: > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> Same issues as I mentioned above for the single-merge-resolution-found case. > - come back to superproject, and `git add <submodule>` to record the above merge "merge or update", right? > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > ------- > > Changes since v2: > There are three major changes in this patch thanks to all the helpful > feedback! I have moved the print function from builtin/merge.c to > merge-ort.c and added it to merge_finalize() in merge-recursive.c and > merge_switch_to_result() in merge-ort.c. This allows other merge > machinery commands besides merge to print submodule conflict advice. > > I have moved the check for submodule conflicts from process_entry() to > merge_submodule(). This also required removing the early returns and > instead going to my submodule conflict check allowing us to pass back > information on whether git detected a possible merge resolution or not. > > I have also updated the error message to better reflect the merge > status. Specifically, if git detects a possible merge resolution, the > error message now also suggest updating to one of those resolutions. > > Other small changes: > - Moved fields that hold submodule conflict information to new object > - Shortened printed commit id to DEFAULT_ABBREV > - Added a test to t6437-submodule-merge.sh for merges with no > resolution existence > - Modified a test in t7402-submodule-rebase to show error message > prints in other parts of the merge machinery > > Changes since v1: > - Removed advice to abort merge > - Error message updated to contain more commit/submodule information > > Signed-off-by: Calvin Wan <calvinwan@google.com> > --- > merge-ort.c | 53 ++++++++++++++++++++++++++++++++++--- > merge-recursive.c | 26 +++++++++++++++--- > merge-recursive.h | 18 +++++++++++++ > t/t6437-submodule-merge.sh | 36 ++++++++++++++++++++++--- > t/t7402-submodule-rebase.sh | 6 +++-- > 5 files changed, 125 insertions(+), 14 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 0d3f42592f..2d9f03ea8c 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -1610,6 +1610,9 @@ static int merge_submodule(struct merge_options *opt, > struct commit *commit_o, *commit_a, *commit_b; > int parent_count; > struct object_array merges; > + struct conflicted_submodule_list *csub = opt->conflicted_submodules; > + struct conflicted_submodule_item csub_item; > + int resolution_exists = 0; > > int i; > int search = !opt->priv->call_depth; > @@ -1619,17 +1622,17 @@ static int merge_submodule(struct merge_options *opt, > > /* we can not handle deletion conflicts */ > if (is_null_oid(o)) > - return 0; > + goto ret; > if (is_null_oid(a)) > - return 0; > + goto ret; > if (is_null_oid(b)) > - return 0; > + goto ret; > > if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { > path_msg(opt, path, 0, > _("Failed to merge submodule %s (not checked out)"), > path); > - return 0; > + goto ret; > } > > if (!(commit_o = lookup_commit_reference(&subrepo, o)) || > @@ -1703,6 +1706,7 @@ static int merge_submodule(struct merge_options *opt, > "which will accept this suggestion.\n"), > oid_to_hex(&merges.objects[0].item->oid), path); > strbuf_release(&sb); > + resolution_exists = 1; > break; > default: > for (i = 0; i < merges.nr; i++) > @@ -1712,11 +1716,24 @@ static int merge_submodule(struct merge_options *opt, > _("Failed to merge submodule %s, but multiple " > "possible merges exist:\n%s"), path, sb.buf); > strbuf_release(&sb); > + resolution_exists = 1; > } > > object_array_clear(&merges); > cleanup: > repo_clear(&subrepo); > +ret: > + if (!ret) { > + if (!csub) { > + CALLOC_ARRAY(csub, 1); > + } > + csub_item.oid = *result; Shouldn't this be "*b" rather than "*result"? Also, are we risking telling the user to "merge commit 0000000000000000000000000000000000000000" from the submodule, given some of the early exits that you redirected to come through here? > + csub_item.path = xstrdup(path); > + csub_item.resolution_exists = resolution_exists; > + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); > + csub->items[csub->nr++] = csub_item; > + opt->conflicted_submodules = csub; > + } > return ret; > } > > @@ -4256,6 +4273,32 @@ static int record_conflicted_index_entries(struct merge_options *opt) > return errs; > } > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { Maybe just make this function be static? > + if (csub && csub->nr > 0) { > + int i; > + printf(_("Recursive merging with submodules is currently not supported.\n" > + "To manually complete the merge:\n")); > + for (i = 0; i < csub->nr; i++) { > + const struct object_id id = csub->items[i].oid; > + if (csub->items[i].resolution_exists) > + printf(_(" - go to submodule (%s), and either update the submodule " > + "to a possible commit above or merge commit %s\n"), > + csub->items[i].path, > + find_unique_abbrev(&id, DEFAULT_ABBREV)); Shouldn't this be repo_find_unique_abbrev(), and in particular with the submodule repository being passed to it? Or perhaps using the format_commit() function, since merge_submodule() is already using it for the earlier submodule-related messages? > + else > + printf(_(" - go to submodule (%s), and merge commit %s\n"), > + csub->items[i].path, > + find_unique_abbrev(&id, DEFAULT_ABBREV)); Likewise? > + } > + printf(_(" - come back to superproject, and `git add")); > + for (i = 0; i < csub->nr; i++) > + printf(_(" %s"), csub->items[i].path); > + printf(_("` to record the above merge \n" > + " - resolve any other conflicts in the superproject\n" > + " - commit the resulting index in the superproject\n")); > + } > +} > + > void merge_switch_to_result(struct merge_options *opt, > struct tree *head, > struct merge_result *result, > @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt, > } > string_list_clear(&olist, 0); > > + print_submodule_conflict_suggestion(opt->conflicted_submodules); > + > /* Also include needed rename limit adjustment now */ > diff_warn_rename_limit("merge.renamelimit", > opti->renames.needed_limit, 0); > diff --git a/merge-recursive.c b/merge-recursive.c > index fd1bbde061..311cc37886 100644 > --- a/merge-recursive.c > +++ b/merge-recursive.c Is it worth updating merge-recursive.c here? I'd rather only give it security fix updates until we delete it. > @@ -1190,6 +1190,9 @@ static int merge_submodule(struct merge_options *opt, > struct commit *commit_base, *commit_a, *commit_b; > int parent_count; > struct object_array merges; > + struct conflicted_submodule_list *csub = opt->conflicted_submodules; > + struct conflicted_submodule_item csub_item; > + int resolution_exists = 0; > > int i; > int search = !opt->priv->call_depth; > @@ -1204,15 +1207,15 @@ static int merge_submodule(struct merge_options *opt, > > /* we can not handle deletion conflicts */ > if (is_null_oid(base)) > - return 0; > + goto ret; > if (is_null_oid(a)) > - return 0; > + goto ret; > if (is_null_oid(b)) > - return 0; > + goto ret; > > if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { > output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path); > - return 0; > + goto ret; > } > > if (!(commit_base = lookup_commit_reference(&subrepo, base)) || > @@ -1287,17 +1290,31 @@ static int merge_submodule(struct merge_options *opt, > " git update-index --cacheinfo 160000 %s \"%s\"\n\n" > "which will accept this suggestion.\n"), > oid_to_hex(&merges.objects[0].item->oid), path); > + resolution_exists = 1; > break; > > default: > output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path); > for (i = 0; i < merges.nr; i++) > print_commit(&subrepo, (struct commit *) merges.objects[i].item); > + resolution_exists = 1; > } > > object_array_clear(&merges); > cleanup: > repo_clear(&subrepo); > +ret: > + if (!ret) { > + if (!csub) { > + CALLOC_ARRAY(csub, 1); > + } > + csub_item.oid = *result; > + csub_item.path = xstrdup(path); > + csub_item.resolution_exists = resolution_exists; > + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); > + csub->items[csub->nr++] = csub_item; > + opt->conflicted_submodules = csub; > + } > return ret; > } > > @@ -3736,6 +3753,7 @@ static void merge_finalize(struct merge_options *opt) > flush_output(opt); > if (!opt->priv->call_depth && opt->buffer_output < 2) > strbuf_release(&opt->obuf); > + print_submodule_conflict_suggestion(opt->conflicted_submodules); > if (show(opt, 2)) > diff_warn_rename_limit("merge.renamelimit", > opt->priv->needed_rename_limit, 0); > diff --git a/merge-recursive.h b/merge-recursive.h > index b88000e3c2..b615955fb7 100644 > --- a/merge-recursive.h > +++ b/merge-recursive.h > @@ -9,6 +9,7 @@ struct object_id; > struct repository; > struct tree; > > +struct conflicted_submodule_list; > struct merge_options_internal; > struct merge_options { > struct repository *repo; > @@ -51,6 +52,21 @@ struct merge_options { > > /* internal fields used by the implementation */ > struct merge_options_internal *priv; > + > + /* field that holds submodule conflict information */ > + struct conflicted_submodule_list *conflicted_submodules; I think this should be added to merge_options_internal rather than to merge_options. There's no need for an API caller to make use of these. Also, if a clear need arises later for API callers to make use of this information, then it should be part of merge_result for merge-ort.c, not part of merge_options. > +}; > + > +struct conflicted_submodule_item { > + struct object_id oid; > + char *path; > + int resolution_exists; > +}; > + > +struct conflicted_submodule_list { > + struct conflicted_submodule_item *items; > + size_t nr; > + size_t alloc; > }; Similarly, I think these should be defined in merge-ort.c (and maybe also merge-recursive.c) near struct merge_options_internal. > void init_merge_options(struct merge_options *opt, struct repository *repo); > @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt, > const struct object_id **merge_bases, > struct commit **result); > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub); and I think there's no reason to put this in the header file; it should just be a static function in merge-ort.c. (And, if we really want to update merge-recursive.c, then copy the function over there. *Nothing* in merge-ort.c should call a function in merge-recursive.c and similarly nothing in merge-recursive.c should call any function in merge-ort.c. Yes, that implies some duplication. There is a fair amount already and we've discussed it, and chosen against creating a shared module as well. I want merge-recursive to not be broken by merge-ort updates; it should remain stable until the day we finally get to nuke it. I'm personally of the opinion that merge-recursive should only get security fixes at this point, though I haven't pinged to see if other folks agree with that point of view yet or not. I'm not wasting my time fixing/improving it, though...) > + > #endif > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index 178413c22f..f0a31c3a61 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -103,8 +103,28 @@ test_expect_success 'setup for merge search' ' > echo "file-c" > file-c && > git add file-c && > git commit -m "sub-c") && > - git commit -a -m "c" && > + git commit -a -m "c") > +' > + > +test_expect_success 'merging should conflict for non fast-forward' ' > + (cd merge-search && > + git checkout -b test-nonforward-a b && > + (cd sub && > + git rev-parse --short sub-b > ../expect) && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + test_must_fail git merge c >actual > + else > + test_must_fail git merge c 2> actual > + fi && > + grep $(cat expect) actual > /dev/null && > + sub_expect="go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-b)" && > + grep "$sub_expect" actual && > + git reset --hard) > +' > > +test_expect_success 'finish setup for merge-search' ' > + (cd merge-search && > git checkout -b d a && > (cd sub && > git checkout -b sub-d sub-b && > @@ -129,9 +149,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' > test_cmp expect actual) > ' > > -test_expect_success 'merging should conflict for non fast-forward' ' > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' > (cd merge-search && > - git checkout -b test-nonforward b && > + git checkout -b test-nonforward-b b && > (cd sub && > git rev-parse sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > @@ -141,6 +161,9 @@ test_expect_success 'merging should conflict for non fast-forward' ' > test_must_fail git merge c 2> actual > fi && > grep $(cat expect) actual > /dev/null && > + sub_expect="go to submodule (sub), and either update the submodule to a \ > +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)" && > + grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -167,6 +190,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > fi && > grep $(cat expect1) actual > /dev/null && > grep $(cat expect2) actual > /dev/null && > + sub_expect="go to submodule (sub), and either update the submodule to a \ > +possible commit above or merge commit $(git -C sub rev-parse --short sub-b)" && > + grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -205,7 +231,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' > git commit -a -m "f" && > > git checkout -b test-backward e && > - test_must_fail git merge f) > + test_must_fail git merge f >actual && > + sub_expect="go to submodule (sub), and merge commit $(git -C sub rev-parse --short sub-a)" && > + grep "$sub_expect" actual) > ' > > > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh > index 8e32f19007..21c8b4e97c 100755 > --- a/t/t7402-submodule-rebase.sh > +++ b/t/t7402-submodule-rebase.sh > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' > test_tick && > git commit -m fourth && > > - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && > + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && > git ls-files -s submodule >actual && > ( > cd submodule && > @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' ' > echo "160000 $(git rev-parse HEAD^^) 2 submodule" && > echo "160000 $(git rev-parse HEAD) 3 submodule" > ) >expect && > - test_cmp expect actual > + test_cmp expect actual && > + sub_expect="go to submodule (submodule), and merge commit $(git -C submodule rev-parse --short HEAD^^)" && > + grep "$sub_expect" actual_output > ' > > test_done > > base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 > -- > 2.37.0.rc0.161.g10f37bed90-goog Despite my many nitpicks and whatnot, it looks like your change will make things nicer for the user, and I think your patch is coming along nicely. There are some things to fix up, but the overall direction seems good. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-30 2:40 ` Elijah Newren @ 2022-06-30 19:48 ` Calvin Wan 2022-07-01 4:27 ` Elijah Newren 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-06-30 19:48 UTC (permalink / raw) To: git, Elijah Newren; +Cc: Calvin Wan, Glen Choo, Junio C Hamano, Philippe Blain > > Failed to merge submodule <submodule> > > CONFLICT (submodule): Merge conflict in <submodule> > > Automatic merge failed; recursive merging with submodules is currently > > not supported. To manually complete the merge: > > - go to submodule (<submodule>), and merge commit <commit> > > I'm still a little unsure on this. The merge_submodule() logic we > have may not have detected a merge commit that merges the two branches > together, but that doesn't automatically imply a new merge must be > created in the submodule to resolve this conflict. There might be > various reasons that other existing commits in the submodule could be > used as the "correct" update. > > Perhaps that is uncommon enough to not be worth mentioning; I'm just a > little hesitant to treat the merge_submodule() logic as robust and > finding the only choices users might want to use. If we do keep the > wording as-is, it might be nice to at least discuss in the commit > message why we chose that and ignored the or-update-submodule option > in this case. You make a good point about merge_submodule() possibly not finding all of the right choices -- I'll add back the or-update-submodule option back. > > If this is correct simply add it to the index for example > > by using: > > > > git update-index --cacheinfo 160000 <commit> "<submodule>" > > > > which will accept this suggestion. > > The last few lines above will no longer be part of the output once > en/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove > command-line-centric submodule message from merge-ort, 2022-06-18). ack > > CONFLICT (submodule): Merge conflict in <submodule> > > Recursive merging with submodules is currently not supported. > > To manually complete the merge: > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > Again, I'm hesitant to treat the suggestions from merge_submodule() as > the only possibilities. For example, perhaps the user will want to > pick a commit that contains one of the ones found by merge_submodule() > in its history -- perhaps because the newer commit they want to select > contains an important bugfix for an issue not discovered at the time > of the merge in the submodule. > > Also, I'm worried your sentence may be easy to misunderstand, due to > it being ambiguous whether "merge" is a verb or an adjective. More > precisely, your sentence could be read as "update the submodule to a > possible commit above, or update the submodule to merge commit > <commit>" and then users say, "But <commit> isn't a merge commit; why > does this message claim it is? Do I just update the submodule to that > commit anyway?" Or perhaps users just update it to <commit> without > even checking to find out that it's not a merge commit, with the > deleterious consequences of missing all the important changes > currently found in the submodule. I agree this sentence can be misinterpreted. I also think the main reason my current message is unclear is because there is not enough context for the user to understand why the merge failed. In a standard merge, the reason in "CONFLICT (<reason>)" provides context, whereas in this case, "CONFLICT (submodule)" only tells the user that the submodule is conflicted in some way. The user is unaware that we attempted to fast-forward the submodule, failed, and now require the user to either update the commit or merge the commit. How does this rewording sound? Automatic merge failed; recursive merging with submodules currently only supports fast-forward merges. For each conflicted submodule, if the current commit does not reflect the desired state, either update or merge the commit. This can be accomplished with the following steps: - go to submodule (<submodule>), and either update the commit or merge commit <commit> > > - come back to superproject, and `git add <submodule>` to record the above merge > > "...to record the above merge or update"? will... "update" haha > > If git detects multiple possible merge resolutions, the following is printed: > > > > -------- > > > > Failed to merge submodule sub, but multiple possible merges exist: > > <commit> Merge branch '<branch1>' into <branch2> > > <commit> Merge branch '<branch1>' into <branch3> > > > > CONFLICT (submodule): Merge conflict in <submodule> > > Recursive merging with submodules is currently not supported. > > To manually complete the merge: > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > Same issues as I mentioned above for the single-merge-resolution-found case. > ditto > > - come back to superproject, and `git add <submodule>` to record the above merge > > "merge or update", right? ditto > > +ret: > > + if (!ret) { > > + if (!csub) { > > + CALLOC_ARRAY(csub, 1); > > + } > > + csub_item.oid = *result; > > Shouldn't this be "*b" rather than "*result"? Yes it should > > Also, are we risking telling the user to "merge commit > 0000000000000000000000000000000000000000" from the submodule, given > some of the early exits that you redirected to come through here? 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? > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { > > Maybe just make this function be static? It should be static now that this won't be called in merge-recursive > > + find_unique_abbrev(&id, DEFAULT_ABBREV)); > > Shouldn't this be repo_find_unique_abbrev(), and in particular with > the submodule repository being passed to it? Or perhaps using the > format_commit() function, since merge_submodule() is already using it > for the earlier submodule-related messages? It should and I will go with the format_commit() option so I don't have to pass subrepo information into the print function. > > void merge_switch_to_result(struct merge_options *opt, > > struct tree *head, > > struct merge_result *result, > > @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt, > > } > > string_list_clear(&olist, 0); > > > > + print_submodule_conflict_suggestion(opt->conflicted_submodules); > > + > > /* Also include needed rename limit adjustment now */ > > diff_warn_rename_limit("merge.renamelimit", > > opti->renames.needed_limit, 0); > > diff --git a/merge-recursive.c b/merge-recursive.c > > index fd1bbde061..311cc37886 100644 > > --- a/merge-recursive.c > > +++ b/merge-recursive.c > > Is it worth updating merge-recursive.c here? I'd rather only give it > security fix updates until we delete it. Ah wasn't aware that was the status of merge-recursive. Will delete. > > diff --git a/merge-recursive.h b/merge-recursive.h > > index b88000e3c2..b615955fb7 100644 > > --- a/merge-recursive.h > > +++ b/merge-recursive.h > > @@ -9,6 +9,7 @@ struct object_id; > > struct repository; > > struct tree; > > > > +struct conflicted_submodule_list; > > struct merge_options_internal; > > struct merge_options { > > struct repository *repo; > > @@ -51,6 +52,21 @@ struct merge_options { > > > > /* internal fields used by the implementation */ > > struct merge_options_internal *priv; > > + > > + /* field that holds submodule conflict information */ > > + struct conflicted_submodule_list *conflicted_submodules; > > I think this should be added to merge_options_internal rather than to > merge_options. There's no need for an API caller to make use of > these. > > Also, if a clear need arises later for API callers to make use of this > information, then it should be part of merge_result for merge-ort.c, > not part of merge_options. ack > > +}; > > + > > +struct conflicted_submodule_item { > > + struct object_id oid; > > + char *path; > > + int resolution_exists; > > +}; > > + > > +struct conflicted_submodule_list { > > + struct conflicted_submodule_item *items; > > + size_t nr; > > + size_t alloc; > > }; > > Similarly, I think these should be defined in merge-ort.c (and maybe > also merge-recursive.c) near struct merge_options_internal. ack > > void init_merge_options(struct merge_options *opt, struct repository *repo); > > @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt, > > const struct object_id **merge_bases, > > struct commit **result); > > > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub); > > and I think there's no reason to put this in the header file; it > should just be a static function in merge-ort.c. (And, if we really > want to update merge-recursive.c, then copy the function over there. > *Nothing* in merge-ort.c should call a function in merge-recursive.c > and similarly nothing in merge-recursive.c should call any function in > merge-ort.c. Yes, that implies some duplication. There is a fair > amount already and we've discussed it, and chosen against creating a > shared module as well. I want merge-recursive to not be broken by > merge-ort updates; it should remain stable until the day we finally > get to nuke it. I'm personally of the opinion that merge-recursive > should only get security fixes at this point, though I haven't pinged > to see if other folks agree with that point of view yet or not. I'm > not wasting my time fixing/improving it, though...) Good to know going forward I should only update merge-ort (unless for security fixes). I'll keep this in mind refactoring my patch. > Despite my many nitpicks and whatnot, it looks like your change will > make things nicer for the user, and I think your patch is coming along > nicely. There are some things to fix up, but the overall direction > seems good. Thank you for all of the helpful feedback! ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-30 19:48 ` Calvin Wan @ 2022-07-01 4:27 ` Elijah Newren 0 siblings, 0 replies; 60+ messages in thread From: Elijah Newren @ 2022-07-01 4:27 UTC (permalink / raw) To: Calvin Wan; +Cc: Git Mailing List, Glen Choo, Junio C Hamano, Philippe Blain On Thu, Jun 30, 2022 at 12:48 PM Calvin Wan <calvinwan@google.com> wrote: > > > > Failed to merge submodule <submodule> > > > CONFLICT (submodule): Merge conflict in <submodule> > > > Automatic merge failed; recursive merging with submodules is currently > > > not supported. To manually complete the merge: > > > - go to submodule (<submodule>), and merge commit <commit> > > > > I'm still a little unsure on this. The merge_submodule() logic we > > have may not have detected a merge commit that merges the two branches > > together, but that doesn't automatically imply a new merge must be > > created in the submodule to resolve this conflict. There might be > > various reasons that other existing commits in the submodule could be > > used as the "correct" update. > > > > Perhaps that is uncommon enough to not be worth mentioning; I'm just a > > little hesitant to treat the merge_submodule() logic as robust and > > finding the only choices users might want to use. If we do keep the > > wording as-is, it might be nice to at least discuss in the commit > > message why we chose that and ignored the or-update-submodule option > > in this case. > > You make a good point about merge_submodule() possibly not finding all > of the right choices -- I'll add back the or-update-submodule option > back. > > > > If this is correct simply add it to the index for example > > > by using: > > > > > > git update-index --cacheinfo 160000 <commit> "<submodule>" > > > > > > which will accept this suggestion. > > > > The last few lines above will no longer be part of the output once > > en/merge-tree is merged; see commit a4040cfa35 (merge-ort: remove > > command-line-centric submodule message from merge-ort, 2022-06-18). > > ack > > > > CONFLICT (submodule): Merge conflict in <submodule> > > > Recursive merging with submodules is currently not supported. > > > To manually complete the merge: > > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > > > Again, I'm hesitant to treat the suggestions from merge_submodule() as > > the only possibilities. For example, perhaps the user will want to > > pick a commit that contains one of the ones found by merge_submodule() > > in its history -- perhaps because the newer commit they want to select > > contains an important bugfix for an issue not discovered at the time > > of the merge in the submodule. > > > > Also, I'm worried your sentence may be easy to misunderstand, due to > > it being ambiguous whether "merge" is a verb or an adjective. More > > precisely, your sentence could be read as "update the submodule to a > > possible commit above, or update the submodule to merge commit > > <commit>" and then users say, "But <commit> isn't a merge commit; why > > does this message claim it is? Do I just update the submodule to that > > commit anyway?" Or perhaps users just update it to <commit> without > > even checking to find out that it's not a merge commit, with the > > deleterious consequences of missing all the important changes > > currently found in the submodule. I probably should have mentioned that listing "merge" first and "update" second in your instructions at least avoids the ambiguity because it makes it clear that "merge" is a verb that way: - go to submodule (<submodule>), and either merge commit <commit> or update the submodule to a possible commit above > I agree this sentence can be misinterpreted. I also think the main > reason my current message is unclear is because there is not enough > context for the user to understand why the merge failed. In a standard > merge, the reason in "CONFLICT (<reason>)" provides context, whereas in > this case, "CONFLICT (submodule)" only tells the user that the submodule > is conflicted in some way. The user is unaware that we attempted to > fast-forward the submodule, failed, and now require the user to either > update the commit or merge the commit. How does this rewording sound? Do they need to know what we attempted? I'm not sure why that matters, beyond maybe telling them that actual automatic merging of submodules is currently only done by Git's merging machinery in very trivial cases. All that should really matter at this point is that there was a submodule modified on both sides of history, and we need them to handle the merge of the submodule. > Automatic merge failed; recursive merging with submodules currently > only supports fast-forward merges. For each conflicted submodule, if I'd rather substitute "trivial cases" instead of "fast-forward merges". For example, we handle deletions on both sides, it's just that that's done before it ever gets to merge_submodule(). And if we add more types of special cases beyond fast-forwards at some point, it'd be nice to not need to update this text. > the current commit does not reflect the desired state, either update or > merge the commit. This can be accomplished with the following steps: Maybe replace the last two sentences with "Please manually handle the merging of each conflicted submodule; this can be accomplished with the following steps:" > - go to submodule (<submodule>), and either update the commit or merge commit <commit> What would you think of switching this to - go to submodule <submodule>, and either merge commit <commit> or update to an existing commit which has merged those changes such as one of the ones listed above > > > - come back to superproject, and `git add <submodule>` to record the above merge > > > > "...to record the above merge or update"? > > will... "update" haha :-) > > > If git detects multiple possible merge resolutions, the following is printed: > > > > > > -------- > > > > > > Failed to merge submodule sub, but multiple possible merges exist: > > > <commit> Merge branch '<branch1>' into <branch2> > > > <commit> Merge branch '<branch1>' into <branch3> > > > > > > CONFLICT (submodule): Merge conflict in <submodule> > > > Recursive merging with submodules is currently not supported. > > > To manually complete the merge: > > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > > > Same issues as I mentioned above for the single-merge-resolution-found case. > > > > ditto > > > > - come back to superproject, and `git add <submodule>` to record the above merge > > > > "merge or update", right? > > ditto > > > > +ret: > > > + if (!ret) { > > > + if (!csub) { > > > + CALLOC_ARRAY(csub, 1); > > > + } > > > + csub_item.oid = *result; > > > > Shouldn't this be "*b" rather than "*result"? > > Yes it should > > > > > Also, are we risking telling the user to "merge commit > > 0000000000000000000000000000000000000000" from the submodule, given > > some of the early exits that you redirected to come through here? > > 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? o is the version in the merge-base, a is the version from the first parent (thus the submodule version stored within HEAD), and b is the version from the second parent (thus the submodule version stored within the main module's MERGE_HEAD commit). We can't have all three be null, because that would just mean there was never a submodule at this path. We can't have two of the three be null, because that would either be deleted on both sides, or added on one side, and those cases are trivially resolvable elsewhere in the merge machinery and there's no need to call merge_submodule(). If o is null, the submodule didn't exist in the merge base. So it must be added on both sides (and the two sides have to have different submodule commits, or the merge of the submodule would be trivially handle-able elsewhere). If a is null, it didn't exist in HEAD, meaning it was deleted on our side of history. (And b has to be different than o, or else this would be trivially resolvable as a deletion.) If b is null, then it's similar to the above case, but the submodule was deleted on the other side of history that we are trying to merge into HEAD rather than on HEAD's side of history. However, now that I've said this, I took another look through the code. I think this actually isn't relevant right now. merge_submodule() is only called from handle_content_merge(), which in turn is only called from two places: (1) process_renames(), and (2) the filemask >= 6 section of process_entry(). The process_renames() cases, since we can't detect renames involving submodules, can't involve a case with a null oid for a submodule. And the filemask >= 6 implies that only o could have a null hash. That means the checks for a and b being null oids within merge_submodule will never trigger. And we don't actually run the risk of telling users to merge the all null commit. Any time we have a modify/delete issue with submodules, we'll just end up at the codepath that reports "CONFLICT (modify/delete): ...", and which doesn't mention anything about the path being a submodule, but it really doesn't need to; the text is accurate regardless. > > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { > > > > Maybe just make this function be static? > > It should be static now that this won't be called in merge-recursive > > > > + find_unique_abbrev(&id, DEFAULT_ABBREV)); > > > > Shouldn't this be repo_find_unique_abbrev(), and in particular with > > the submodule repository being passed to it? Or perhaps using the > > format_commit() function, since merge_submodule() is already using it > > for the earlier submodule-related messages? > > It should and I will go with the format_commit() option so I don't have to pass subrepo information into the print function. > > > > void merge_switch_to_result(struct merge_options *opt, > > > struct tree *head, > > > struct merge_result *result, > > > @@ -4324,6 +4367,8 @@ void merge_switch_to_result(struct merge_options *opt, > > > } > > > string_list_clear(&olist, 0); > > > > > > + print_submodule_conflict_suggestion(opt->conflicted_submodules); > > > + > > > /* Also include needed rename limit adjustment now */ > > > diff_warn_rename_limit("merge.renamelimit", > > > opti->renames.needed_limit, 0); > > > diff --git a/merge-recursive.c b/merge-recursive.c > > > index fd1bbde061..311cc37886 100644 > > > --- a/merge-recursive.c > > > +++ b/merge-recursive.c > > > > Is it worth updating merge-recursive.c here? I'd rather only give it > > security fix updates until we delete it. > > Ah wasn't aware that was the status of merge-recursive. Will delete. > > > > diff --git a/merge-recursive.h b/merge-recursive.h > > > index b88000e3c2..b615955fb7 100644 > > > --- a/merge-recursive.h > > > +++ b/merge-recursive.h > > > @@ -9,6 +9,7 @@ struct object_id; > > > struct repository; > > > struct tree; > > > > > > +struct conflicted_submodule_list; > > > struct merge_options_internal; > > > struct merge_options { > > > struct repository *repo; > > > @@ -51,6 +52,21 @@ struct merge_options { > > > > > > /* internal fields used by the implementation */ > > > struct merge_options_internal *priv; > > > + > > > + /* field that holds submodule conflict information */ > > > + struct conflicted_submodule_list *conflicted_submodules; > > > > I think this should be added to merge_options_internal rather than to > > merge_options. There's no need for an API caller to make use of > > these. > > > > Also, if a clear need arises later for API callers to make use of this > > information, then it should be part of merge_result for merge-ort.c, > > not part of merge_options. > > ack > > > > +}; > > > + > > > +struct conflicted_submodule_item { > > > + struct object_id oid; > > > + char *path; > > > + int resolution_exists; > > > +}; > > > + > > > +struct conflicted_submodule_list { > > > + struct conflicted_submodule_item *items; > > > + size_t nr; > > > + size_t alloc; > > > }; > > > > Similarly, I think these should be defined in merge-ort.c (and maybe > > also merge-recursive.c) near struct merge_options_internal. > > ack > > > > void init_merge_options(struct merge_options *opt, struct repository *repo); > > > @@ -122,4 +138,6 @@ int merge_recursive_generic(struct merge_options *opt, > > > const struct object_id **merge_bases, > > > struct commit **result); > > > > > > +void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub); > > > > and I think there's no reason to put this in the header file; it > > should just be a static function in merge-ort.c. (And, if we really > > want to update merge-recursive.c, then copy the function over there. > > *Nothing* in merge-ort.c should call a function in merge-recursive.c > > and similarly nothing in merge-recursive.c should call any function in > > merge-ort.c. Yes, that implies some duplication. There is a fair > > amount already and we've discussed it, and chosen against creating a > > shared module as well. I want merge-recursive to not be broken by > > merge-ort updates; it should remain stable until the day we finally > > get to nuke it. I'm personally of the opinion that merge-recursive > > should only get security fixes at this point, though I haven't pinged > > to see if other folks agree with that point of view yet or not. I'm > > not wasting my time fixing/improving it, though...) > > Good to know going forward I should only update merge-ort (unless for security fixes). I'll keep this in mind refactoring my patch. > > > Despite my many nitpicks and whatnot, it looks like your change will > > make things nicer for the user, and I think your patch is coming along > > nicely. There are some things to fix up, but the overall direction > > seems good. > > Thank you for all of the helpful feedback! ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan 2022-06-30 2:40 ` Elijah Newren @ 2022-06-30 20:35 ` Glen Choo 2022-06-30 20:45 ` Glen Choo 2022-06-30 21:08 ` Calvin Wan 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan 2 siblings, 2 replies; 60+ messages in thread From: Glen Choo @ 2022-06-30 20:35 UTC (permalink / raw) To: Calvin Wan, git; +Cc: gitster, newren, levraiphilippeblain, Calvin Wan Hi! I have a suggestion for the output text; I haven't looked closely at the code changes. Calvin Wan <calvinwan@google.com> writes: > Changes since v2: > [...] > Changes since v1: > [...] I notice that this is all above the "---", i.e. this becomes part of the commit message when "git am"-ed. Intentional? > If git detects a possible merge resolution, the following is printed: > > -------- > > Failed to merge submodule sub, but a possible merge resolution exists: > <commit> Merge branch '<branch1>' into <branch2> > > > If this is correct simply add it to the index for example > by using: > > git update-index --cacheinfo 160000 <commit> "<submodule>" > > which will accept this suggestion. > > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules is currently not supported. > To manually complete the merge: > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > - come back to superproject, and `git add <submodule>` to record the above merge > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > -------- I'm hesitant to recommend a plumbing command like "git update-index" to the user, especially if the user is one who needs help resolving a submodule merge conflict. I also believe this would be the first time we recommend "git update-index". To do this using only porcelain commands, maybe: git -C <submodule> checkout <commit> && git add <submodule> (Though this might need to be broken up into two commands because I'm not sure if we ever include "&&" in a help message. I'm guessing we don't for portability reasons?) > If git detects multiple possible merge resolutions, the following is printed: > > -------- > > Failed to merge submodule sub, but multiple possible merges exist: > <commit> Merge branch '<branch1>' into <branch2> > <commit> Merge branch '<branch1>' into <branch3> > > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules is currently not supported. > To manually complete the merge: > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > - come back to superproject, and `git add <submodule>` to record the above merge > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > ------- For consistency, perhaps include the "here's how to use the suggestion" instructions here as well? ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-30 20:35 ` Glen Choo @ 2022-06-30 20:45 ` Glen Choo 2022-06-30 21:08 ` Calvin Wan 1 sibling, 0 replies; 60+ messages in thread From: Glen Choo @ 2022-06-30 20:45 UTC (permalink / raw) To: Calvin Wan, git; +Cc: gitster, newren, levraiphilippeblain, Calvin Wan Glen Choo <chooglen@google.com> writes: > Hi! I have a suggestion for the output text; I haven't looked closely at > the code changes. > >> If git detects a possible merge resolution, the following is printed: >> >> -------- >> >> Failed to merge submodule sub, but a possible merge resolution exists: >> <commit> Merge branch '<branch1>' into <branch2> >> >> >> If this is correct simply add it to the index for example >> by using: >> >> git update-index --cacheinfo 160000 <commit> "<submodule>" >> >> which will accept this suggestion. >> >> CONFLICT (submodule): Merge conflict in <submodule> >> Recursive merging with submodules is currently not supported. >> To manually complete the merge: >> - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> >> - come back to superproject, and `git add <submodule>` to record the above merge >> - resolve any other conflicts in the superproject >> - commit the resulting index in the superproject >> Automatic merge failed; fix conflicts and then commit the result. >> >> -------- > > I'm hesitant to recommend a plumbing command like "git update-index" to > the user, especially if the user is one who needs help resolving a > submodule merge conflict. I also believe this would be the first time we > recommend "git update-index". > > To do this using only porcelain commands, maybe: > > git -C <submodule> checkout <commit> && > git add <submodule> > > (Though this might need to be broken up into two commands because I'm > not sure if we ever include "&&" in a help message. I'm guessing we > don't for portability reasons?) Gah, ignore everything I said here. I should have read the thread closer: - The update-index suggestion didn't come from you; it had already existed prior to this series. - Both Philppe and Elijah have already suggested the exact same thing. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v3] submodule merge: update conflict error message 2022-06-30 20:35 ` Glen Choo 2022-06-30 20:45 ` Glen Choo @ 2022-06-30 21:08 ` Calvin Wan 1 sibling, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-06-30 21:08 UTC (permalink / raw) To: Glen Choo; +Cc: git, gitster, newren, levraiphilippeblain > I notice that this is all above the "---", i.e. this becomes part of the > commit message when "git am"-ed. Intentional? Good catch. Not intentional at all. On Thu, Jun 30, 2022 at 1:35 PM Glen Choo <chooglen@google.com> wrote: > > Hi! I have a suggestion for the output text; I haven't looked closely at > the code changes. > > Calvin Wan <calvinwan@google.com> writes: > > > Changes since v2: > > [...] > > Changes since v1: > > [...] > > I notice that this is all above the "---", i.e. this becomes part of the > commit message when "git am"-ed. Intentional? > > > If git detects a possible merge resolution, the following is printed: > > > > -------- > > > > Failed to merge submodule sub, but a possible merge resolution exists: > > <commit> Merge branch '<branch1>' into <branch2> > > > > > > If this is correct simply add it to the index for example > > by using: > > > > git update-index --cacheinfo 160000 <commit> "<submodule>" > > > > which will accept this suggestion. > > > > CONFLICT (submodule): Merge conflict in <submodule> > > Recursive merging with submodules is currently not supported. > > To manually complete the merge: > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > - come back to superproject, and `git add <submodule>` to record the above merge > > - resolve any other conflicts in the superproject > > - commit the resulting index in the superproject > > Automatic merge failed; fix conflicts and then commit the result. > > > > -------- > > I'm hesitant to recommend a plumbing command like "git update-index" to > the user, especially if the user is one who needs help resolving a > submodule merge conflict. I also believe this would be the first time we > recommend "git update-index". > > To do this using only porcelain commands, maybe: > > git -C <submodule> checkout <commit> && > git add <submodule> > > (Though this might need to be broken up into two commands because I'm > not sure if we ever include "&&" in a help message. I'm guessing we > don't for portability reasons?) > > > If git detects multiple possible merge resolutions, the following is printed: > > > > -------- > > > > Failed to merge submodule sub, but multiple possible merges exist: > > <commit> Merge branch '<branch1>' into <branch2> > > <commit> Merge branch '<branch1>' into <branch3> > > > > CONFLICT (submodule): Merge conflict in <submodule> > > Recursive merging with submodules is currently not supported. > > To manually complete the merge: > > - go to submodule (<submodule>), and either update the submodule to a possible commit above or merge commit <commit> > > - come back to superproject, and `git add <submodule>` to record the above merge > > - resolve any other conflicts in the superproject > > - commit the resulting index in the superproject > > Automatic merge failed; fix conflicts and then commit the result. > > > > ------- > > For consistency, perhaps include the "here's how to use the suggestion" > instructions here as well? ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4] submodule merge: update conflict error message 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan 2022-06-30 2:40 ` Elijah Newren 2022-06-30 20:35 ` Glen Choo @ 2022-07-12 23:19 ` Calvin Wan 2022-07-13 18:11 ` Junio C Hamano ` (2 more replies) 2 siblings, 3 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-12 23:19 UTC (permalink / raw) To: git; +Cc: Calvin Wan, chooglen, gitster, newren, levraiphilippeblain Apologies to any reviewers for the late v4 -- was OOO Changes since v3: Thank you again Elijah for the helpful feedback! I have removed any code touching merge-recursive.c, and refactored the rest into merge-ort.c. The error message has been updated as well as any relevant test cases. I had added a jump in v3 to "ret:" in merge_submodule() to accomodate early returns, but this has been proven to not be necessary since an early return means the submodule was either renamed or deleted, and this case is already taken care of with the message "CONFLICT (modify/delete):" == Description == When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded or trivially resolved, the merge fails and git prints the following error: Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; fix conflicts and then commit the result. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to match the steps above. If git does not detect a merge resolution, the following is printed: ==== Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ==== If git detects a possible merge resolution, the following is printed: ==== Failed to merge submodule sub, but a possible merge resolution exists: <commit> Merge branch '<branch1>' into <branch2> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: To manually complete the merge: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes such as one listed above - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ==== If git detects multiple possible merge resolutions, the following is printed: ==== Failed to merge submodule sub, but multiple possible merges exist: <commit> Merge branch '<branch1>' into <branch2> <commit> Merge branch '<branch1>' into <branch3> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: To manually complete the merge: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes such as one listed above - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. == Previous Changes == Changes since v2: There are three major changes in this patch thanks to all the helpful feedback! I have moved the print function from builtin/merge.c to merge-ort.c and added it to merge_finalize() in merge-recursive.c and merge_switch_to_result() in merge-ort.c. This allows other merge machinery commands besides merge to print submodule conflict advice. I have moved the check for submodule conflicts from process_entry() to merge_submodule(). This also required removing the early returns and instead going to my submodule conflict check allowing us to pass back information on whether git detected a possible merge resolution or not. I have also updated the error message to better reflect the merge status. Specifically, if git detects a possible merge resolution, the error message now also suggest updating to one of those resolutions. Other small changes: - Moved fields that hold submodule conflict information to new object - Shortened printed commit id to DEFAULT_ABBREV - Added a test to t6437-submodule-merge.sh for merges with no resolution existence - Modified a test in t7402-submodule-rebase to show error message prints in other parts of the merge machinery Changes since v1: - Removed advice to abort merge - Error message updated to contain more commit/submodule information Signed-off-by: Calvin Wan <calvinwan@google.com> --- merge-ort.c | 56 +++++++++++++++++++++++++++++++++++++ t/t6437-submodule-merge.sh | 31 +++++++++++++++++--- t/t7402-submodule-rebase.sh | 6 ++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 0d3f42592f..872d924f58 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -292,6 +292,18 @@ struct rename_info { int needed_limit; }; +struct conflicted_submodule_item { + char *oid; + char *path; + int resolution_exists; +}; + +struct conflicted_submodule_list { + struct conflicted_submodule_item *items; + size_t nr; + size_t alloc; +}; + struct merge_options_internal { /* * paths: primary data structure in all of merge ort. @@ -385,6 +397,9 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct conflicted_submodule_list conflicted_submodules; }; struct version_info { @@ -1610,6 +1625,9 @@ static int merge_submodule(struct merge_options *opt, struct commit *commit_o, *commit_a, *commit_b; int parent_count; struct object_array merges; + struct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules; + struct conflicted_submodule_item csub_item; + int resolution_exists = 0; int i; int search = !opt->priv->call_depth; @@ -1703,6 +1721,7 @@ static int merge_submodule(struct merge_options *opt, "which will accept this suggestion.\n"), oid_to_hex(&merges.objects[0].item->oid), path); strbuf_release(&sb); + resolution_exists = 1; break; default: for (i = 0; i < merges.nr; i++) @@ -1712,10 +1731,22 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s, but multiple " "possible merges exist:\n%s"), path, sb.buf); strbuf_release(&sb); + resolution_exists = 1; } object_array_clear(&merges); cleanup: + if (!ret) { + if (!csub) { + CALLOC_ARRAY(csub, 1); + } + csub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); + csub_item.path = xstrdup(path); + csub_item.resolution_exists = resolution_exists; + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); + csub->items[csub->nr++] = csub_item; + opt->priv->conflicted_submodules = *csub; + } repo_clear(&subrepo); return ret; } @@ -4256,6 +4287,29 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { + if (csub && csub->nr > 0) { + int i; + printf(_("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")); + for (i = 0; i < csub->nr; i++) { + printf(_(" - go to submodule (%s), and either merge commit %s\n" + "or update to an existing commit which has merged those changes\n"), + csub->items[i].path, + csub->items[i].oid); + if (csub->items[i].resolution_exists) + printf(_("such as one listed above\n")); + } + printf(_(" - come back to superproject, and `git add")); + for (i = 0; i < csub->nr; i++) + printf(_(" %s"), csub->items[i].path); + printf(_("` to record the above merge or update \n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n")); + } +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result, @@ -4324,6 +4378,8 @@ void merge_switch_to_result(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 178413c22f..cc1a312661 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' + +test_expect_success 'merging should conflict for non fast-forward' ' + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual + else + test_must_fail git merge c 2> actual + fi && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual && + git reset --hard) +' +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort @@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' ' test_must_fail git merge c 2> actual fi && grep $(cat expect) actual > /dev/null && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual && git reset --hard) ' @@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' ' fi && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual && git reset --hard) ' @@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual) ' diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..f1bb29681f 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output ' test_done base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 -- 2.37.0.144.g8ac04bfd2-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan @ 2022-07-13 18:11 ` Junio C Hamano 2022-07-17 2:46 ` Elijah Newren 2022-07-15 12:57 ` Johannes Schindelin 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan 2 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-07-13 18:11 UTC (permalink / raw) To: Calvin Wan; +Cc: git, chooglen, newren, levraiphilippeblain Calvin Wan <calvinwan@google.com> writes: > Changes since v3: > Thank you again Elijah for the helpful feedback! I have removed any code > touching merge-recursive.c, and refactored the rest into merge-ort.c. > The error message has been updated as well as any relevant test cases. I > had added a jump in v3 to "ret:" in merge_submodule() to accomodate > early returns, but this has been proven to not be necessary since an > early return means the submodule was either renamed or deleted, and this > case is already taken care of with the message "CONFLICT (modify/delete):" Unfortunately a34edae6 (merge-ort: split out a separate display_update_messages() function, 2022-06-18) moves the code this patch touches to a separate function. If this is now an ort-specific topic by dropping merge-recursive support, perhaps we'd need to coordinate with the other branch. I think en/merge-tree is ready to be merged down to 'master' in a few days, so I'll wait for [v5] of this patch after that happens. Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-13 18:11 ` Junio C Hamano @ 2022-07-17 2:46 ` Elijah Newren 0 siblings, 0 replies; 60+ messages in thread From: Elijah Newren @ 2022-07-17 2:46 UTC (permalink / raw) To: Junio C Hamano; +Cc: Calvin Wan, Git Mailing List, Glen Choo, Philippe Blain On Wed, Jul 13, 2022 at 11:11 AM Junio C Hamano <gitster@pobox.com> wrote: > > Calvin Wan <calvinwan@google.com> writes: > > > Changes since v3: > > Thank you again Elijah for the helpful feedback! I have removed any code > > touching merge-recursive.c, and refactored the rest into merge-ort.c. > > The error message has been updated as well as any relevant test cases. I > > had added a jump in v3 to "ret:" in merge_submodule() to accomodate > > early returns, but this has been proven to not be necessary since an > > early return means the submodule was either renamed or deleted, and this > > case is already taken care of with the message "CONFLICT (modify/delete):" > > Unfortunately a34edae6 (merge-ort: split out a separate > display_update_messages() function, 2022-06-18) moves the code this > patch touches to a separate function. > > If this is now an ort-specific topic by dropping merge-recursive > support, perhaps we'd need to coordinate with the other branch. > > I think en/merge-tree is ready to be merged down to 'master' in a > few days, so I'll wait for [v5] of this patch after that happens. Yeah, sorry about that Calvin; one of the risks of working in the same area. Should be pretty easy to resolve, though; let me know if you have any questions when you go to re-roll. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan 2022-07-13 18:11 ` Junio C Hamano @ 2022-07-15 12:57 ` Johannes Schindelin 2022-07-16 6:22 ` Junio C Hamano ` (2 more replies) 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan 2 siblings, 3 replies; 60+ messages in thread From: Johannes Schindelin @ 2022-07-15 12:57 UTC (permalink / raw) To: Calvin Wan; +Cc: git, chooglen, gitster, newren, levraiphilippeblain Hi Calvin, On Tue, 12 Jul 2022, Calvin Wan wrote: > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index 178413c22f..cc1a312661 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' > echo "file-c" > file-c && > git add file-c && > git commit -m "sub-c") && > - git commit -a -m "c" && > + git commit -a -m "c") > +' > + > +test_expect_success 'merging should conflict for non fast-forward' ' > + (cd merge-search && > + git checkout -b test-nonforward-a b && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + test_must_fail git merge c >actual > + else > + test_must_fail git merge c 2> actual > + fi && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual && No matter how hard I tried to stare at the code, no matter whether I looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only the `ort` code has been taught this new trick. In fact, when I run this locally with `GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here: https://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833 and here: https://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820), it also leaves no error message in `actual`. So you probably want to move the `sub_expect` assignment and the `grep` into the `ort` clause of the conditional above. With this fixup, things seem to work over here: -- snip -- diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index cc1a3126619..3892d0bf742 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' ' git checkout -b test-nonforward-a b && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual && git reset --hard) ' @@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex git rev-parse sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && grep $(cat expect) actual > /dev/null && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual && git reset --hard) ' @@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual && git reset --hard) ' @@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' ' git checkout -b test-backward e && test_must_fail git merge f >actual && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && - grep "$sub_expect" actual) + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' -- snap -- > + git reset --hard) It would be better to move this before the subshell like this: test_when_finished "git -C merge-search reset --hard" && Ciao, Dscho > +' > > +test_expect_success 'finish setup for merge-search' ' > + (cd merge-search && > git checkout -b d a && > (cd sub && > git checkout -b sub-d sub-b && > @@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' > test_cmp expect actual) > ' > > -test_expect_success 'merging should conflict for non fast-forward' ' > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' > (cd merge-search && > - git checkout -b test-nonforward b && > + git checkout -b test-nonforward-b b && > (cd sub && > git rev-parse sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > @@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' ' > test_must_fail git merge c 2> actual > fi && > grep $(cat expect) actual > /dev/null && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > fi && > grep $(cat expect1) actual > /dev/null && > grep $(cat expect2) actual > /dev/null && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' > git commit -a -m "f" && > > git checkout -b test-backward e && > - test_must_fail git merge f) > + test_must_fail git merge f >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > + grep "$sub_expect" actual) > ' > > > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh > index 8e32f19007..f1bb29681f 100755 > --- a/t/t7402-submodule-rebase.sh > +++ b/t/t7402-submodule-rebase.sh > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' > test_tick && > git commit -m fourth && > > - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && > + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && > git ls-files -s submodule >actual && > ( > cd submodule && > @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' ' > echo "160000 $(git rev-parse HEAD^^) 2 submodule" && > echo "160000 $(git rev-parse HEAD) 3 submodule" > ) >expect && > - test_cmp expect actual > + test_cmp expect actual && > + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && > + grep "$sub_expect" actual_output > ' > > test_done > > base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 > -- > 2.37.0.144.g8ac04bfd2-goog > > > ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-15 12:57 ` Johannes Schindelin @ 2022-07-16 6:22 ` Junio C Hamano 2022-07-17 2:44 ` Elijah Newren 2022-07-18 17:03 ` Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-07-16 6:22 UTC (permalink / raw) To: Johannes Schindelin Cc: Calvin Wan, git, chooglen, newren, levraiphilippeblain Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: >> +test_expect_success 'merging should conflict for non fast-forward' ' >> + (cd merge-search && >> + git checkout -b test-nonforward-a b && >> + if test "$GIT_TEST_MERGE_ALGORITHM" = ort >> + then >> + test_must_fail git merge c >actual >> + else >> + test_must_fail git merge c 2> actual >> + fi && >> + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && >> + grep "$sub_expect" actual && > > No matter how hard I tried to stare at the code, no matter whether I > looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this > `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only > the `ort` code has been taught this new trick. Indeed. https://github.com/git/git/runs/7366982085 is how it failed in 'seen'. Thanks for helping Calvin. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-15 12:57 ` Johannes Schindelin 2022-07-16 6:22 ` Junio C Hamano @ 2022-07-17 2:44 ` Elijah Newren 2022-07-18 17:03 ` Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Elijah Newren @ 2022-07-17 2:44 UTC (permalink / raw) To: Johannes Schindelin Cc: Calvin Wan, Git Mailing List, Glen Choo, Junio C Hamano, Philippe Blain On Fri, Jul 15, 2022 at 5:57 AM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote: > > Hi Calvin, > > On Tue, 12 Jul 2022, Calvin Wan wrote: > > > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > > index 178413c22f..cc1a312661 100755 > > --- a/t/t6437-submodule-merge.sh > > +++ b/t/t6437-submodule-merge.sh > > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' > > echo "file-c" > file-c && > > git add file-c && > > git commit -m "sub-c") && > > - git commit -a -m "c" && > > + git commit -a -m "c") > > +' > > + > > +test_expect_success 'merging should conflict for non fast-forward' ' > > + (cd merge-search && > > + git checkout -b test-nonforward-a b && > > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > > + then > > + test_must_fail git merge c >actual > > + else > > + test_must_fail git merge c 2> actual > > + fi && > > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > > + grep "$sub_expect" actual && > > No matter how hard I tried to stare at the code, no matter whether I > looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this > `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only > the `ort` code has been taught this new trick. Right, that's left over from an earlier version of the patch (which did also modify merge-recursive). > In fact, when I run this locally with > `GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here: > https://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833 > and here: > https://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820), > it also leaves no error message in `actual`. > > So you probably want to move the `sub_expect` assignment and the `grep` > into the `ort` clause of the conditional above. > > With this fixup, things seem to work over here: > > -- snip -- > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index cc1a3126619..3892d0bf742 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' ' > git checkout -b test-nonforward-a b && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex > git rev-parse sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > grep $(cat expect) actual > /dev/null && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > ) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > grep $(cat expect1) actual > /dev/null && > grep $(cat expect2) actual > /dev/null && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' ' > > git checkout -b test-backward e && > test_must_fail git merge f >actual && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > - grep "$sub_expect" actual) > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > + grep "$sub_expect" actual > + fi) > ' > > -- snap -- Yeah, this looks good, thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v4] submodule merge: update conflict error message 2022-07-15 12:57 ` Johannes Schindelin 2022-07-16 6:22 ` Junio C Hamano 2022-07-17 2:44 ` Elijah Newren @ 2022-07-18 17:03 ` Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-18 17:03 UTC (permalink / raw) To: Johannes Schindelin; +Cc: git, chooglen, gitster, newren, levraiphilippeblain > No matter how hard I tried to stare at the code, no matter whether I > looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this > `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only > the `ort` code has been taught this new trick. I definitely overlooked this removing the merge-recursive section of my code. Thanks for catching this! On Fri, Jul 15, 2022 at 5:57 AM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote: > > Hi Calvin, > > On Tue, 12 Jul 2022, Calvin Wan wrote: > > > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > > index 178413c22f..cc1a312661 100755 > > --- a/t/t6437-submodule-merge.sh > > +++ b/t/t6437-submodule-merge.sh > > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' > > echo "file-c" > file-c && > > git add file-c && > > git commit -m "sub-c") && > > - git commit -a -m "c" && > > + git commit -a -m "c") > > +' > > + > > +test_expect_success 'merging should conflict for non fast-forward' ' > > + (cd merge-search && > > + git checkout -b test-nonforward-a b && > > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > > + then > > + test_must_fail git merge c >actual > > + else > > + test_must_fail git merge c 2> actual > > + fi && > > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > > + grep "$sub_expect" actual && > > No matter how hard I tried to stare at the code, no matter whether I > looked at `cw/submodule-merge-messages` or `seen`, I cannot see how this > `grep` could ever succeed when `GIT_TEST_MERGE_ALGORITHM=recursive`: only > the `ort` code has been taught this new trick. > > In fact, when I run this locally with > `GIT_TEST_MERGE_ALGORITHM=recursive`, it not only fails (as shown here: > https://github.com/gitgitgadget/git/runs/7349612861?check_suite_focus=true#step:4:1833 > and here: > https://github.com/gitgitgadget/git/runs/7326925485?check_suite_focus=true#step:4:1820), > it also leaves no error message in `actual`. > > So you probably want to move the `sub_expect` assignment and the `grep` > into the `ort` clause of the conditional above. > > With this fixup, things seem to work over here: > > -- snip -- > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index cc1a3126619..3892d0bf742 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -111,12 +111,12 @@ test_expect_success 'merging should conflict for non fast-forward' ' > git checkout -b test-nonforward-a b && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -153,13 +153,13 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex > git rev-parse sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > grep $(cat expect) actual > /dev/null && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -180,14 +180,14 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > ) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > grep $(cat expect1) actual > /dev/null && > grep $(cat expect2) actual > /dev/null && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > - grep "$sub_expect" actual && > git reset --hard) > ' > > @@ -227,8 +227,11 @@ test_expect_success 'merging should fail for changes that are backwards' ' > > git checkout -b test-backward e && > test_must_fail git merge f >actual && > - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > - grep "$sub_expect" actual) > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > + grep "$sub_expect" actual > + fi) > ' > > -- snap -- > > > + git reset --hard) > > It would be better to move this before the subshell like this: > > test_when_finished "git -C merge-search reset --hard" && > > Ciao, > Dscho > > > +' > > > > +test_expect_success 'finish setup for merge-search' ' > > + (cd merge-search && > > git checkout -b d a && > > (cd sub && > > git checkout -b sub-d sub-b && > > @@ -129,9 +146,9 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' > > test_cmp expect actual) > > ' > > > > -test_expect_success 'merging should conflict for non fast-forward' ' > > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' > > (cd merge-search && > > - git checkout -b test-nonforward b && > > + git checkout -b test-nonforward-b b && > > (cd sub && > > git rev-parse sub-d > ../expect) && > > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > > @@ -141,6 +158,8 @@ test_expect_success 'merging should conflict for non fast-forward' ' > > test_must_fail git merge c 2> actual > > fi && > > grep $(cat expect) actual > /dev/null && > > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > > + grep "$sub_expect" actual && > > git reset --hard) > > ' > > > > @@ -167,6 +186,8 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > > fi && > > grep $(cat expect1) actual > /dev/null && > > grep $(cat expect2) actual > /dev/null && > > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > > + grep "$sub_expect" actual && > > git reset --hard) > > ' > > > > @@ -205,7 +226,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' > > git commit -a -m "f" && > > > > git checkout -b test-backward e && > > - test_must_fail git merge f) > > + test_must_fail git merge f >actual && > > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > > + grep "$sub_expect" actual) > > ' > > > > > > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh > > index 8e32f19007..f1bb29681f 100755 > > --- a/t/t7402-submodule-rebase.sh > > +++ b/t/t7402-submodule-rebase.sh > > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' > > test_tick && > > git commit -m fourth && > > > > - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && > > + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && > > git ls-files -s submodule >actual && > > ( > > cd submodule && > > @@ -112,7 +112,9 @@ test_expect_success 'rebasing submodule that should conflict' ' > > echo "160000 $(git rev-parse HEAD^^) 2 submodule" && > > echo "160000 $(git rev-parse HEAD) 3 submodule" > > ) >expect && > > - test_cmp expect actual > > + test_cmp expect actual && > > + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && > > + grep "$sub_expect" actual_output > > ' > > > > test_done > > > > base-commit: ab336e8f1c8009c8b1aab8deb592148e69217085 > > -- > > 2.37.0.144.g8ac04bfd2-goog > > > > > > ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v5] submodule merge: update conflict error message 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan 2022-07-13 18:11 ` Junio C Hamano 2022-07-15 12:57 ` Johannes Schindelin @ 2022-07-18 21:43 ` Calvin Wan 2022-07-19 6:39 ` Junio C Hamano ` (4 more replies) 2 siblings, 5 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-18 21:43 UTC (permalink / raw) To: git Cc: Calvin Wan, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin Changes since v4: - Rebased onto gitster/master - Fixed test cases to work with only merge-ort (and not merge-recursive) == Description == When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded or trivially resolved, the merge fails and git prints the following error: Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Automatic merge failed; fix conflicts and then commit the result. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to match the steps above. If git does not detect a merge resolution, the following is printed: ==== Failed to merge submodule <submodule> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ==== If git detects a possible merge resolution, the following is printed: ==== Failed to merge submodule sub, but a possible merge resolution exists: <commit> Merge branch '<branch1>' into <branch2> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: To manually complete the merge: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes such as one listed above - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ==== If git detects multiple possible merge resolutions, the following is printed: ==== Failed to merge submodule sub, but multiple possible merges exist: <commit> Merge branch '<branch1>' into <branch2> <commit> Merge branch '<branch1>' into <branch3> CONFLICT (submodule): Merge conflict in <submodule> Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: To manually complete the merge: - go to submodule (<submodule>), and either merge commit <commit> or update to an existing commit which has merged those changes such as one listed above - come back to superproject, and `git add <submodule>` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. == Previous Changes == Changes since v3: Thank you again Elijah for the helpful feedback! I have removed any code touching merge-recursive.c, and refactored the rest into merge-ort.c. The error message has been updated as well as any relevant test cases. I had added a jump in v3 to "ret:" in merge_submodule() to accomodate early returns, but this has been proven to not be necessary since an early return means the submodule was either renamed or deleted, and this case is already taken care of with the message "CONFLICT (modify/delete):" Changes since v2: There are three major changes in this patch thanks to all the helpful feedback! I have moved the print function from builtin/merge.c to merge-ort.c and added it to merge_finalize() in merge-recursive.c and merge_switch_to_result() in merge-ort.c. This allows other merge machinery commands besides merge to print submodule conflict advice. I have moved the check for submodule conflicts from process_entry() to merge_submodule(). This also required removing the early returns and instead going to my submodule conflict check allowing us to pass back information on whether git detected a possible merge resolution or not. I have also updated the error message to better reflect the merge status. Specifically, if git detects a possible merge resolution, the error message now also suggest updating to one of those resolutions. Other small changes: - Moved fields that hold submodule conflict information to new object - Shortened printed commit id to DEFAULT_ABBREV - Added a test to t6437-submodule-merge.sh for merges with no resolution existence - Modified a test in t7402-submodule-rebase to show error message prints in other parts of the merge machinery Changes since v1: - Removed advice to abort merge - Error message updated to contain more commit/submodule information Signed-off-by: Calvin Wan <calvinwan@google.com> --- merge-ort.c | 56 +++++++++++++++++++++++++++++++++++++ t/t6437-submodule-merge.sh | 38 +++++++++++++++++++++---- t/t7402-submodule-rebase.sh | 9 ++++-- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 01f150ef3b..125ee3c0d1 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -292,6 +292,18 @@ struct rename_info { int needed_limit; }; +struct conflicted_submodule_item { + char *oid; + char *path; + int resolution_exists; +}; + +struct conflicted_submodule_list { + struct conflicted_submodule_item *items; + size_t nr; + size_t alloc; +}; + struct merge_options_internal { /* * paths: primary data structure in all of merge ort. @@ -387,6 +399,9 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct conflicted_submodule_list conflicted_submodules; }; struct version_info { @@ -1741,6 +1756,9 @@ static int merge_submodule(struct merge_options *opt, struct commit *commit_o, *commit_a, *commit_b; int parent_count; struct object_array merges; + struct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules; + struct conflicted_submodule_item csub_item; + int resolution_exists = 0; int i; int search = !opt->priv->call_depth; @@ -1835,6 +1853,7 @@ static int merge_submodule(struct merge_options *opt, "resolution exists: %s"), path, sb.buf); strbuf_release(&sb); + resolution_exists = 1; break; default: for (i = 0; i < merges.nr; i++) @@ -1845,10 +1864,22 @@ static int merge_submodule(struct merge_options *opt, _("Failed to merge submodule %s, but multiple " "possible merges exist:\n%s"), path, sb.buf); strbuf_release(&sb); + resolution_exists = 1; } object_array_clear(&merges); cleanup: + if (!ret) { + if (!csub) { + CALLOC_ARRAY(csub, 1); + } + csub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); + csub_item.path = xstrdup(path); + csub_item.resolution_exists = resolution_exists; + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); + csub->items[csub->nr++] = csub_item; + opt->priv->conflicted_submodules = *csub; + } repo_clear(&subrepo); return ret; } @@ -4412,6 +4443,29 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { + if (csub && csub->nr > 0) { + int i; + printf(_("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")); + for (i = 0; i < csub->nr; i++) { + printf(_(" - go to submodule (%s), and either merge commit %s\n" + "or update to an existing commit which has merged those changes\n"), + csub->items[i].path, + csub->items[i].oid); + if (csub->items[i].resolution_exists) + printf(_("such as one listed above\n")); + } + printf(_(" - come back to superproject, and `git add")); + for (i = 0; i < csub->nr; i++) + printf(_(" %s"), csub->items[i].path); + printf(_("` to record the above merge or update \n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n")); + } +} + void merge_display_update_messages(struct merge_options *opt, int detailed, struct merge_result *result) @@ -4461,6 +4515,8 @@ void merge_display_update_messages(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c253bf759a..84913525cf 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' +test_expect_success 'merging should conflict for non fast-forward' ' + test_when_finished "git -C merge-search reset --hard" && + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + else + test_must_fail git merge c 2> actual + fi) +' + +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..ebeca12a71 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output + fi ' test_done base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a -- 2.37.0.170.g444d1eabd0-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan @ 2022-07-19 6:39 ` Junio C Hamano 2022-07-19 19:30 ` Calvin Wan 2022-07-19 7:13 ` Junio C Hamano ` (3 subsequent siblings) 4 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-07-19 6:39 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin Calvin Wan <calvinwan@google.com> writes: > Changes since v4: > - Rebased onto gitster/master > - Fixed test cases to work with only merge-ort (and not merge-recursive) That's not a log-message material. You could have probably written scissors ... > == Description == --- >8 --- ... here, perhaps? > When attempting to merge in a superproject with conflicting submodule > pointers that cannot be fast-forwarded or trivially resolved, the merge > fails and git prints the following error: > > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Automatic merge failed; fix conflicts and then commit the result. > > Git is left in a conflicted state, which requires the user to: > 1. merge submodules or update submodules to an already existing > commit that reflects the merge > 2. add submodules changes to the superproject > 3. finish merging superproject > These steps are non-obvious for newer submodule users to figure out > based on the error message and neither `git submodule status` nor `git > status` provide any useful pointers. > > Update error message to match the steps above. If git does not detect a > merge resolution, the following is printed: > > ==== > > Failed to merge submodule <submodule> > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules currently only supports trivial cases. > Please manually handle the merging of each conflicted submodule. > This can be accomplished with the following steps: > - go to submodule (<submodule>), and either merge commit <commit> > or update to an existing commit which has merged those changes > - come back to superproject, and `git add <submodule>` to record the above merge or update > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > ==== > > If git detects a possible merge resolution, the following is printed: > > ==== > > Failed to merge submodule sub, but a possible merge resolution exists: > <commit> Merge branch '<branch1>' into <branch2> > > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules currently only supports trivial cases. > Please manually handle the merging of each conflicted submodule. > This can be accomplished with the following steps: > To manually complete the merge: > - go to submodule (<submodule>), and either merge commit <commit> > or update to an existing commit which has merged those changes > such as one listed above > - come back to superproject, and `git add <submodule>` to record the above merge or update > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > ==== > > If git detects multiple possible merge resolutions, the following is printed: > > ==== > > Failed to merge submodule sub, but multiple possible merges exist: > <commit> Merge branch '<branch1>' into <branch2> > <commit> Merge branch '<branch1>' into <branch3> > > CONFLICT (submodule): Merge conflict in <submodule> > Recursive merging with submodules currently only supports trivial cases. > Please manually handle the merging of each conflicted submodule. > This can be accomplished with the following steps: > To manually complete the merge: > - go to submodule (<submodule>), and either merge commit <commit> > or update to an existing commit which has merged those changes > such as one listed above > - come back to superproject, and `git add <submodule>` to record the above merge or update > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. But cutting the cruft at the top is still not enough, because the below are not log-message material, either. These extraneous stuff may help reviewers while the patch is being polished, but once committed to our history, readers of "git log" should not have to care what other attempts were made that did not become part of the final hstory. The log message proper should be understandable standalone. The stuff to help reviewers who may have seen earlier round are usually written in the cover letter, or after the three-dash line. > == Previous Changes == > > Changes since v3: > ... > > Signed-off-by: Calvin Wan <calvinwan@google.com> > --- > merge-ort.c | 56 +++++++++++++++++++++++++++++++++++++ > t/t6437-submodule-merge.sh | 38 +++++++++++++++++++++---- > t/t7402-submodule-rebase.sh | 9 ++++-- > 3 files changed, 95 insertions(+), 8 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 01f150ef3b..125ee3c0d1 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > ... > + if (csub && csub->nr > 0) { > + int i; > + printf(_("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")); This makes me wonder if these "helpful but verbose" messages should use the advise mechanism. Also, those reviewers who care about l10n may suggest use of printf_ln() to lose the LF at the end of these messages (i.e. not just the above one, but others we see below as well). > + for (i = 0; i < csub->nr; i++) { > + printf(_(" - go to submodule (%s), and either merge commit %s\n" > + "or update to an existing commit which has merged those changes\n"), > + csub->items[i].path, > + csub->items[i].oid); > + if (csub->items[i].resolution_exists) > + printf(_("such as one listed above\n")); > + } > + printf(_(" - come back to superproject, and `git add")); > + for (i = 0; i < csub->nr; i++) > + printf(_(" %s"), csub->items[i].path); > + printf(_("` to record the above merge or update \n" > + " - resolve any other conflicts in the superproject\n" > + " - commit the resulting index in the superproject\n")); > + } > +} > + > void merge_display_update_messages(struct merge_options *opt, > int detailed, > struct merge_result *result) > @@ -4461,6 +4515,8 @@ void merge_display_update_messages(struct merge_options *opt, > } > string_list_clear(&olist, 0); > > + print_submodule_conflict_suggestion(&opti->conflicted_submodules); > + > /* Also include needed rename limit adjustment now */ > diff_warn_rename_limit("merge.renamelimit", > opti->renames.needed_limit, 0); Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-19 6:39 ` Junio C Hamano @ 2022-07-19 19:30 ` Calvin Wan 2022-07-19 20:16 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-07-19 19:30 UTC (permalink / raw) To: Junio C Hamano Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin > The stuff to help reviewers who may have seen earlier round are > usually written in the cover letter, or after the three-dash line. Ah I see since this patch doesn't have a cover letter, I should put all the reviewer-centric stuff after the three-dash line. > > + if (csub && csub->nr > 0) { > > + int i; > > + printf(_("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")); > > This makes me wonder if these "helpful but verbose" messages should > use the advise mechanism. I agree. The only loss of information if someone turned off this message would be the commit id that possibly needs to be merged. > Also, those reviewers who care about l10n > may suggest use of printf_ln() to lose the LF at the end of these > messages (i.e. not just the above one, but others we see below as > well). ack ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-19 19:30 ` Calvin Wan @ 2022-07-19 20:16 ` Junio C Hamano 0 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-07-19 20:16 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin Calvin Wan <calvinwan@google.com> writes: >> The stuff to help reviewers who may have seen earlier round are >> usually written in the cover letter, or after the three-dash line. > Ah I see since this patch doesn't have a cover letter, I should put > all the reviewer-centric stuff after the three-dash line. > >> > + if (csub && csub->nr > 0) { >> > + int i; >> > + printf(_("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")); >> >> This makes me wonder if these "helpful but verbose" messages should >> use the advise mechanism. > > I agree. The only loss of information if someone turned off this message > would be the commit id that possibly needs to be merged. If the commit found by find_first_merges() are useful, then it is losing information, so it is one argument against using the advise mechanism. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan 2022-07-19 6:39 ` Junio C Hamano @ 2022-07-19 7:13 ` Junio C Hamano 2022-07-19 19:07 ` Calvin Wan 2022-07-25 6:05 ` Ævar Arnfjörð Bjarmason ` (2 subsequent siblings) 4 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-07-19 7:13 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin Calvin Wan <calvinwan@google.com> writes: > @@ -1835,6 +1853,7 @@ static int merge_submodule(struct merge_options *opt, > "resolution exists: %s"), > path, sb.buf); > strbuf_release(&sb); > + resolution_exists = 1; > break; > default: > for (i = 0; i < merges.nr; i++) > @@ -1845,10 +1864,22 @@ static int merge_submodule(struct merge_options *opt, > _("Failed to merge submodule %s, but multiple " > "possible merges exist:\n%s"), path, sb.buf); > strbuf_release(&sb); > + resolution_exists = 1; > } These work on the result of calling find_first_merges(), but is it possible that we are asked to call this function more than once because we see conflicted submodule updates at two or more paths? I may be misreading the code, but find_first_merges(), either the version we see in this file, or the one in merge-recursive.c, or its original introduced in 68d03e4a (Implement automatic fast-forward merge for submodules, 2010-07-07), look safe to be called twice. It runs the get_revision() machinery, smudging the object flags while walking the history, but I do not see any code that cleans up these flags for the second traversal. Also, this is not a new problem, but I am afraid that the logic to find existing merges in find_first_merges() might be overly loose. It tries to find existing merges that can reach the two commits, and then finds, among these merges, the one that is not descendant of any other such merges. Don't we end up finding a merge M A---o---M / B when a superproject merge needs a merge of A and B in the submodule? That is certainly a merge that contains both A and B and it may be closer to A and B than any other existing merges, but it still may not be a merge between A and B (in the depicted case, an extra commit 'o' nobody ordered is included for free in the result). I am not seeing how existing code tries to avoid such a situation. Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-19 7:13 ` Junio C Hamano @ 2022-07-19 19:07 ` Calvin Wan 2022-07-19 20:30 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-07-19 19:07 UTC (permalink / raw) To: Junio C Hamano Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin > These work on the result of calling find_first_merges(), but is it > possible that we are asked to call this function more than once > because we see conflicted submodule updates at two or more paths? This does get called multiple times if we see conflicted submodule updates at two or more paths. > I may be misreading the code, but find_first_merges(), either the > version we see in this file, or the one in merge-recursive.c, or its > original introduced in 68d03e4a (Implement automatic fast-forward > merge for submodules, 2010-07-07), look safe to be called twice. It > runs the get_revision() machinery, smudging the object flags while > walking the history, but I do not see any code that cleans up these > flags for the second traversal. I don't quite understand which flags need to be cleaned up for the second traversal. > Also, this is not a new problem, but I am afraid that the logic to > find existing merges in find_first_merges() might be overly loose. > It tries to find existing merges that can reach the two commits, and > then finds, among these merges, the one that is not descendant of > any other such merges. Don't we end up finding a merge M > > A---o---M > / > B > > when a superproject merge needs a merge of A and B in the submodule? > That is certainly a merge that contains both A and B and it may be > closer to A and B than any other existing merges, but it still may > not be a merge between A and B (in the depicted case, an extra > commit 'o' nobody ordered is included for free in the result). I am > not seeing how existing code tries to avoid such a situation. It is true that we find merge M and it isn't representative of a merge of A and B in the submodule. In this case, the existing code prints: "Failed to merge submodule %s, but a possible merge resolution exists: %s" While this part doesn't claim M to be a guaranteed merge resolution, my change adds this line: "or update to an existing commit which has merged those changes such as one listed above" Instead of adding more verbosity to this language, it seems like a better idea to remove "such as one listed above" entirely (and subsequently any of my code that flags merge resolutions). ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-19 19:07 ` Calvin Wan @ 2022-07-19 20:30 ` Junio C Hamano 0 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-07-19 20:30 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, newren, levraiphilippeblain, Johannes.Schindelin Calvin Wan <calvinwan@google.com> writes: >> These work on the result of calling find_first_merges(), but is it >> possible that we are asked to call this function more than once >> because we see conflicted submodule updates at two or more paths? > > This does get called multiple times if we see conflicted submodule > updates at two or more paths. > >> I may be misreading the code, but find_first_merges(), either the >> version we see in this file, or the one in merge-recursive.c, or its >> original introduced in 68d03e4a (Implement automatic fast-forward >> merge for submodules, 2010-07-07), look safe to be called twice. It >> runs the get_revision() machinery, smudging the object flags while >> walking the history, but I do not see any code that cleans up these >> flags for the second traversal. > > I don't quite understand which flags need to be cleaned up for the > second traversal. UNINTERESTING, TREESAME, ADDED, SEEN, SHOWN are among the flags used by the object walk (if MyFirstObjectWalk does not talk about them, it probably should), and they need to be cleared before you prepare a new "struct rev_info" and throw it at setup_revisions(), prepare_revision_walk(), and start calling get_revision(). submodule.c::collect_changed_submodules() has its own revision walk, but it calls reset_revision_walk() to clear these flags from all objects in the superproject (i.e. the_repository). I _think_ the reason why this never turned out to be a problem in practice is because we do not run this helper twice for the same submodule. Even though we may smudge many objects from a submodule with an object walk without clearing their flags, as long as we run the next object walk in a different submodule whose object flags are still unsmudged, it would be OK. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan 2022-07-19 6:39 ` Junio C Hamano 2022-07-19 7:13 ` Junio C Hamano @ 2022-07-25 6:05 ` Ævar Arnfjörð Bjarmason 2022-07-25 12:11 ` Ævar Arnfjörð Bjarmason 2022-07-25 12:31 ` Ævar Arnfjörð Bjarmason 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan 4 siblings, 1 reply; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-07-25 6:05 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin On Mon, Jul 18 2022, Calvin Wan wrote: One of the CI failures in "seen" is because of my topic to mark (among other things) t1500*.sh as passing with SANITIZE=leak, and this change. Because... > .. > object_array_clear(&merges); > cleanup: > + if (!ret) { > + if (!csub) { > + CALLOC_ARRAY(csub, 1); > + } > + csub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); > + csub_item.path = xstrdup(path); > + csub_item.resolution_exists = resolution_exists; > + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); ... in "cleanup" we're ALLOC_GROW()-ing? I haven't looked into this yet, but this seems susppect. This is line 1879 in the following stacktrace: + git -C super merge branch1 Failed to merge submodule dir/sub CONFLICT (submodule): Merge conflict in dir/sub Recursive merging with submodules currently only supports trivial cases. Please manually handle the merging of each conflicted submodule. This can be accomplished with the following steps: - go to submodule (dir/sub), and either merge commit 7018b5f or update to an existing commit which has merged those changes - come back to superproject, and `git add dir/sub` to record the above merge or update - resolve any other conflicts in the superproject - commit the resulting index in the superproject Automatic merge failed; fix conflicts and then commit the result. ================================================================= ==31261==ERROR: LeakSanitizer: detected memory leaks Direct leak of 576 byte(s) in 1 object(s) allocated from: #0 0x4565ad in __interceptor_realloc (git+0x4565ad) #1 0x76ecfd in xrealloc wrapper.c:136:8 #2 0x64fcd3 in merge_submodule merge-ort.c:1879:3 #3 0x64ee9b in handle_content_merge merge-ort.c:2118:11 #4 0x651c14 in process_entry merge-ort.c:4056:17 #5 0x648c05 in process_entries merge-ort.c:4267:4 #6 0x646c03 in merge_ort_nonrecursive_internal merge-ort.c:4893:2 #7 0x6470f3 in merge_ort_internal merge-ort.c:4982:2 #8 0x646de0 in merge_incore_recursive merge-ort.c:5033:2 #9 0x652d1a in merge_ort_recursive merge-ort-wrappers.c:57:2 #10 0x4ec0f6 in try_merge_strategy builtin/merge.c:764:12 #11 0x4e9bf2 in cmd_merge builtin/merge.c:1710:9 #12 0x45a3aa in run_builtin git.c:466:11 #13 0x458e41 in handle_builtin git.c:720:3 #14 0x459d85 in run_argv git.c:787:4 #15 0x458bfa in cmd_main git.c:920:19 #16 0x56a049 in main common-main.c:56:11 #17 0x7fe592bca81c in __libc_start_main csu/../csu/libc-start.c:332:16 #18 0x431139 in _start (git+0x431139) ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-25 6:05 ` Ævar Arnfjörð Bjarmason @ 2022-07-25 12:11 ` Ævar Arnfjörð Bjarmason 2022-07-25 22:03 ` Calvin Wan 0 siblings, 1 reply; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-07-25 12:11 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin On Mon, Jul 25 2022, Ævar Arnfjörð Bjarmason wrote: > On Mon, Jul 18 2022, Calvin Wan wrote: > > > One of the CI failures in "seen" is because of my topic to mark (among > other things) t1500*.sh as passing with SANITIZE=leak, and this change. > > Because... > >> .. >> object_array_clear(&merges); >> cleanup: >> + if (!ret) { >> + if (!csub) { >> + CALLOC_ARRAY(csub, 1); >> + } >> + csub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); >> + csub_item.path = xstrdup(path); >> + csub_item.resolution_exists = resolution_exists; >> + ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); > > ... in "cleanup" we're ALLOC_GROW()-ing? I haven't looked into this yet, > but this seems susppect. This is line 1879 in the following stacktrace: > > + git -C super merge branch1 > Failed to merge submodule dir/sub > CONFLICT (submodule): Merge conflict in dir/sub > Recursive merging with submodules currently only supports trivial cases. > Please manually handle the merging of each conflicted submodule. > This can be accomplished with the following steps: > - go to submodule (dir/sub), and either merge commit 7018b5f > or update to an existing commit which has merged those changes > - come back to superproject, and `git add dir/sub` to record the above merge or update > - resolve any other conflicts in the superproject > - commit the resulting index in the superproject > Automatic merge failed; fix conflicts and then commit the result. > > ================================================================= > ==31261==ERROR: LeakSanitizer: detected memory leaks > > Direct leak of 576 byte(s) in 1 object(s) allocated from: > #0 0x4565ad in __interceptor_realloc (git+0x4565ad) > #1 0x76ecfd in xrealloc wrapper.c:136:8 > #2 0x64fcd3 in merge_submodule merge-ort.c:1879:3 > #3 0x64ee9b in handle_content_merge merge-ort.c:2118:11 > #4 0x651c14 in process_entry merge-ort.c:4056:17 > #5 0x648c05 in process_entries merge-ort.c:4267:4 > #6 0x646c03 in merge_ort_nonrecursive_internal merge-ort.c:4893:2 > #7 0x6470f3 in merge_ort_internal merge-ort.c:4982:2 > #8 0x646de0 in merge_incore_recursive merge-ort.c:5033:2 > #9 0x652d1a in merge_ort_recursive merge-ort-wrappers.c:57:2 > #10 0x4ec0f6 in try_merge_strategy builtin/merge.c:764:12 > #11 0x4e9bf2 in cmd_merge builtin/merge.c:1710:9 > #12 0x45a3aa in run_builtin git.c:466:11 > #13 0x458e41 in handle_builtin git.c:720:3 > #14 0x459d85 in run_argv git.c:787:4 > #15 0x458bfa in cmd_main git.c:920:19 > #16 0x56a049 in main common-main.c:56:11 > #17 0x7fe592bca81c in __libc_start_main csu/../csu/libc-start.c:332:16 > #18 0x431139 in _start (git+0x431139) Looking at this a bit more this fixes it, and also the NIH allocation pattern & what we can do with a "struct string_list" instead of managing our own allocations. I did wonder why you need to allocate the "oid" at all, i.e. at the time of merge_submodules() can't we just squirrel away the "const struct object_id *" and only when we emit the message later run repo_find_unique_abbrev()? I think we can do that, but it means re-init-ing the submodule, and I'm not sure if there's any edge cases with the state changing between us trying merge_submodules() and the eventual messages we emit. But it would mean that we'd just need to allocate a pointer to that oid + resolution_exists. Perhaps we could also continue down that path and do this with just one allocation for that "oid/resolution_exists" side-data. I.e. you'd have a second string_list in the struct, have a non-NULL util mean "resolution exists", and the "string" there would be nodup'd (we could refer to the "string" in the first one). But I think all of that probably isn't worth it, but the below seems like a good trade-off & good place to stop, and fixes the memory leak this topic introduces. merge-ort.c | 69 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 154d33f2f45..5cf87f70b58 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -293,16 +293,17 @@ struct rename_info { }; struct conflicted_submodule_item { - char *oid; - char *path; - int resolution_exists; + char *abbrev; + int resolution_exists:1; }; -struct conflicted_submodule_list { - struct conflicted_submodule_item *items; - size_t nr; - size_t alloc; -}; +static void conflicted_submodule_item_free(void *util, const char *str) +{ + struct conflicted_submodule_item *item = util; + + free(item->abbrev); + free(item); +} struct merge_options_internal { /* @@ -401,7 +402,7 @@ struct merge_options_internal { int call_depth; /* field that holds submodule conflict information */ - struct conflicted_submodule_list conflicted_submodules; + struct string_list conflicted_submodules; }; struct version_info { @@ -701,6 +702,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, mem_pool_discard(&opti->pool, 0); + string_list_clear_func(&opti->conflicted_submodules, + conflicted_submodule_item_free); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); renames->callback_data_nr = renames->callback_data_alloc = 0; @@ -1756,8 +1760,6 @@ static int merge_submodule(struct merge_options *opt, struct commit *commit_o, *commit_a, *commit_b; int parent_count; struct object_array merges; - struct conflicted_submodule_list *csub = &opt->priv->conflicted_submodules; - struct conflicted_submodule_item csub_item; int resolution_exists = 0; int i; @@ -1870,15 +1872,17 @@ static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); cleanup: if (!ret) { - if (!csub) { - CALLOC_ARRAY(csub, 1); - } - csub_item.oid = xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); - csub_item.path = xstrdup(path); - csub_item.resolution_exists = resolution_exists; - ALLOC_GROW(csub->items, csub->nr + 1, csub->alloc); - csub->items[csub->nr++] = csub_item; - opt->priv->conflicted_submodules = *csub; + struct string_list *csub = &opt->priv->conflicted_submodules; + struct conflicted_submodule_item *util; + const char *abbrev; + + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);; + + util = xmalloc(sizeof(*util)); + util->abbrev = xstrdup(abbrev); + util->resolution_exists = resolution_exists; + + string_list_append(csub, path)->util = util; } repo_clear(&subrepo); return ret; @@ -4465,23 +4469,26 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } -static void print_submodule_conflict_suggestion(struct conflicted_submodule_list *csub) { - if (csub && csub->nr > 0) { - int i; +static void print_submodule_conflict_suggestion(struct string_list *csub) { + if (csub->nr > 0) { + struct string_list_item *item; + printf(_("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")); - for (i = 0; i < csub->nr; i++) { + + for_each_string_list_item(item, csub) { + struct conflicted_submodule_item *util = item->util; + printf(_(" - go to submodule (%s), and either merge commit %s\n" "or update to an existing commit which has merged those changes\n"), - csub->items[i].path, - csub->items[i].oid); - if (csub->items[i].resolution_exists) + item->string, util->abbrev); + if (util->resolution_exists) printf(_("such as one listed above\n")); } printf(_(" - come back to superproject, and `git add")); - for (i = 0; i < csub->nr; i++) - printf(_(" %s"), csub->items[i].path); + for_each_string_list_item(item, csub) + printf(_(" %s"), item->string); printf(_("` to record the above merge or update \n" " - resolve any other conflicts in the superproject\n" " - commit the resulting index in the superproject\n")); @@ -4538,6 +4545,8 @@ void merge_display_update_messages(struct merge_options *opt, string_list_clear(&olist, 0); print_submodule_conflict_suggestion(&opti->conflicted_submodules); + string_list_clear_func(&opti->conflicted_submodules, + conflicted_submodule_item_free); /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", @@ -4795,6 +4804,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) */ strmap_init(&opt->priv->conflicts); + string_list_init_dup(&opt->priv->conflicted_submodules); + trace2_region_leave("merge", "allocate/init", opt->repo); } ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-25 12:11 ` Ævar Arnfjörð Bjarmason @ 2022-07-25 22:03 ` Calvin Wan 0 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-25 22:03 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin Hi Ævar, Thanks in advance for the suggestions and catching my memory leak. > Perhaps we could also continue down that path and do this with just one > allocation for that "oid/resolution_exists" side-data. I.e. you'd have a > second string_list in the struct, have a non-NULL util mean "resolution > exists", and the "string" there would be nodup'd (we could refer to the > "string" in the first one). Since I am removing the "resolution exists" field, I modified your changes to use the util to only hold the abbrev. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan ` (2 preceding siblings ...) 2022-07-25 6:05 ` Ævar Arnfjörð Bjarmason @ 2022-07-25 12:31 ` Ævar Arnfjörð Bjarmason 2022-07-25 21:27 ` Calvin Wan 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan 4 siblings, 1 reply; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-07-25 12:31 UTC (permalink / raw) To: Calvin Wan Cc: git, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin On Mon, Jul 18 2022, Calvin Wan wrote: I'll have some more comments, but just on the output/i18n: > + printf(_("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")); > + for (i = 0; i < csub->nr; i++) { > + printf(_(" - go to submodule (%s), and either merge commit %s\n" > + "or update to an existing commit which has merged those changes\n"), This seems to have some formatting issues, i.e. we'll emit things like: - one one continued - tow two continued Whereas we want this, surely?; - one one continued - two two continued I.e. we're using " - " to mark list items, but then not indenting the items. > + csub->items[i].path, > + csub->items[i].oid); > + if (csub->items[i].resolution_exists) > + printf(_("such as one listed above\n")); that people might read this backwards You need to consider :) I.e. this is "translation lego" that we try to avoid. It's a bit more verbose (but it often is, unfortunately) I think you can borrow a bit from ba5e8a0eb80 (object-name: make ambiguous object output translatable, 2022-01-27) here, i.e.: 1. Just translate a message like "go to submodule ...\nor update to an", have another variant for the "resolution exists". 2. As translators retain those \n split those lines and format them with e.g. _(" %s") (you could borrow the string used in object-name.c via ba5e8a0eb80), or _("- %s"). This will give you list-itemized output, which will also format correctly in RTL languages, and takes the formatting concerns completely out of the hands of translators, and allows us to change it later ourselves. I.e. we can safely assume that for a \n-delimited translation we can take \n-delimited input from a translator and treat the first line specially as a "first line in new list item". > + } > + printf(_(" - come back to superproject, and `git add")); > + for (i = 0; i < csub->nr; i++) > + printf(_(" %s"), csub->items[i].path); More inconsistent indentation, and per ba5e8a0eb80 you should explain any addition of magical formatting like " %s" with a TRANSLATORS comment. > + printf(_("` to record the above merge or update \n" > + " - resolve any other conflicts in the superproject\n" > + " - commit the resulting index in the superproject\n")); A bit more odd formatting, i.e.: - A trailing " " before a \n? - " " after `, what is that ` doing? At first I thought it was a typo, but looking again it's a continuation of `` from above It's quite odd to tell a user to run a command with the `` syntax, which is used for interpolation. Let's instead suggest: blah blah blah run: git add foo \ bar \ [...] baz I.e. use \ at the end of lines to note a multi-line command, not wrap the whole thing in ``-quotes. - If a message must always end in a \n just add it between _()'s, instead of making it part of the _() string. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v5] submodule merge: update conflict error message 2022-07-25 12:31 ` Ævar Arnfjörð Bjarmason @ 2022-07-25 21:27 ` Calvin Wan 0 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-25 21:27 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, chooglen, gitster, newren, levraiphilippeblain, Johannes.Schindelin Hi Ævar, > Whereas we want this, surely?; > > - one > one continued > - two > two continued > > I.e. we're using " - " to mark list items, but then not indenting the > items. Yes we surely do! > I think you can borrow a bit from ba5e8a0eb80 (object-name: make > ambiguous object output translatable, 2022-01-27) here, i.e.: > > 1. Just translate a message like "go to submodule ...\nor update to > an", have another variant for the "resolution exists". I am removing the "resolution exists" variant because the text that came with it was unclear to begin with. > per ba5e8a0eb80 you should explain > any addition of magical formatting like " %s" with a TRANSLATORS > comment. ack > - A trailing " " before a \n? ack > It's quite odd to tell a user to run a command with the `` syntax, > which is used for interpolation. Let's instead suggest: > > blah blah blah run: > > git add foo \ > bar \ [...] > baz > > I.e. use \ at the end of lines to note a multi-line command, not wrap > the whole thing in ``-quotes. That looks much better than what I currently have. > - If a message must always end in a \n just add it between _()'s, > instead of making it part of the _() string. ack ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v6] submodule merge: update conflict error message 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan ` (3 preceding siblings ...) 2022-07-25 12:31 ` Ævar Arnfjörð Bjarmason @ 2022-07-26 21:00 ` Calvin Wan 2022-07-27 1:13 ` Elijah Newren ` (2 more replies) 4 siblings, 3 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-26 21:00 UTC (permalink / raw) To: git; +Cc: Calvin Wan, gitster, newren, Johannes.Schindelin, avarab When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded or trivially resolved, the merge fails and git prints an error message that accurately describes the failure, but does not provide steps for the user to resolve the error. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to provide steps to resolve submodule merge conflict. Future work could involve adding an advice flag to the message. Although the message is long, it also has the id of the commit that needs to be merged, which could be useful information for the user. Signed-off-by: Calvin Wan <calvinwan@google.com> --- I'm a little unsure on the code style for the `git add` section of the error message. It works, but is tricky for a code reviewer to decipher the formatting. I don't think it should affect translation since the text is only a git command and folder names. Changes since v5: - Fixed memory leak reported by Ævar - Fixed error message formatting and addded TRANSLATOR tags - Removed "resolution exists" merge-ort.c | 61 +++++++++++++++++++++++++++++++++++++ t/t6437-submodule-merge.sh | 38 +++++++++++++++++++---- t/t7402-submodule-rebase.sh | 9 ++++-- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 01f150ef3b..147be0ce31 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -387,6 +387,9 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct string_list conflicted_submodules; }; struct version_info { @@ -686,6 +689,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, mem_pool_discard(&opti->pool, 0); + string_list_clear(&opti->conflicted_submodules, 1); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); renames->callback_data_nr = renames->callback_data_alloc = 0; @@ -1849,6 +1854,17 @@ static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); cleanup: + if (!ret) { + struct string_list *csub = &opt->priv->conflicted_submodules; + char *util; + const char *abbrev; + + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); + util = xstrdup(abbrev); + + string_list_append(csub, path)->util = util; + } + repo_clear(&subrepo); return ret; } @@ -4412,6 +4428,47 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void print_submodule_conflict_suggestion(struct string_list *csub) { + int first = 1; + if (csub->nr > 0) { + struct string_list_item *item; + printf(_("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")); + + for_each_string_list_item(item, csub) { + const char *abbrev= item->util; + /* + * TRANSLATORS: This is a line of advice to resolve a merge conflict + * in a submodule. The second argument is the abbreviated id of the + * commit that needs to be merged. + * E.g. - go to submodule (sub), and either merge commit abc1234" + */ + printf(_(" - go to submodule (%s), and either merge commit %s\n" + " or update to an existing commit which has merged those changes\n"), + item->string, abbrev); + } + printf(_(" - come back to superproject and run:\n\n")); + for_each_string_list_item(item, csub) + /* + * TRANSLATORS: This is a line of a recommended `git add` command + * with multiple lines of submodule folders. + * E.g.: git add sub \ + * sub2 \ + * sub3 + */ + if (first) { + printf(" git add %s", item->string); + first = 0; + } else { + printf(" \\\n %s", item->string); + } + printf(_("\n\n to record the above merge or update\n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n")); + } +} + void merge_display_update_messages(struct merge_options *opt, int detailed, struct merge_result *result) @@ -4461,6 +4518,9 @@ void merge_display_update_messages(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + string_list_clear(&opti->conflicted_submodules, 1); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); @@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_enter("merge", "allocate/init", opt->repo); if (opt->priv) { clear_or_reinit_internal_opts(opt->priv, 1); + string_list_init_dup(&opt->priv->conflicted_submodules); trace2_region_leave("merge", "allocate/init", opt->repo); return; } diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c253bf759a..84913525cf 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' +test_expect_success 'merging should conflict for non fast-forward' ' + test_when_finished "git -C merge-search reset --hard" && + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + else + test_must_fail git merge c 2> actual + fi) +' + +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..ebeca12a71 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output + fi ' test_done base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a -- 2.37.1.359.gd136c6c3e2-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v6] submodule merge: update conflict error message 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan @ 2022-07-27 1:13 ` Elijah Newren 2022-07-27 22:00 ` Calvin Wan 2022-07-27 9:20 ` Ævar Arnfjörð Bjarmason 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan 2 siblings, 1 reply; 60+ messages in thread From: Elijah Newren @ 2022-07-27 1:13 UTC (permalink / raw) To: Calvin Wan Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð On Tue, Jul 26, 2022 at 2:00 PM Calvin Wan <calvinwan@google.com> wrote: > > When attempting to merge in a superproject with conflicting submodule > pointers that cannot be fast-forwarded or trivially resolved, the merge > fails and git prints an error message that accurately describes the micro nit: s/git/Git/ (feel free to ignore since you're already at v6) > failure, but does not provide steps for the user to resolve the error. > > Git is left in a conflicted state, which requires the user to: > 1. merge submodules or update submodules to an already existing > commit that reflects the merge > 2. add submodules changes to the superproject > 3. finish merging superproject > These steps are non-obvious for newer submodule users to figure out > based on the error message and neither `git submodule status` nor `git > status` provide any useful pointers. > > Update error message to provide steps to resolve submodule merge > conflict. Future work could involve adding an advice flag to the > message. Although the message is long, it also has the id of the > commit that needs to be merged, which could be useful information > for the user. Well explained. One very minor suggestion: perhaps change "id of the commit" to "id of the submodule commit" just to make it slightly clearer that this information would take work for the user to discover on their own? (When I first read it, I was thinking, "but they have the commit, it's what they passed to merge", before I realized my error.) > Signed-off-by: Calvin Wan <calvinwan@google.com> > --- > > I'm a little unsure on the code style for the `git add` section of > the error message. It works, but is tricky for a code reviewer > to decipher the formatting. I don't think it should affect > translation since the text is only a git command and folder names. > > Changes since v5: > - Fixed memory leak reported by Ævar > - Fixed error message formatting and addded TRANSLATOR tags > - Removed "resolution exists" > > merge-ort.c | 61 +++++++++++++++++++++++++++++++++++++ > t/t6437-submodule-merge.sh | 38 +++++++++++++++++++---- > t/t7402-submodule-rebase.sh | 9 ++++-- > 3 files changed, 100 insertions(+), 8 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 01f150ef3b..147be0ce31 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -387,6 +387,9 @@ struct merge_options_internal { > > /* call_depth: recursion level counter for merging merge bases */ > int call_depth; > + > + /* field that holds submodule conflict information */ > + struct string_list conflicted_submodules; > }; > > struct version_info { > @@ -686,6 +689,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, > > mem_pool_discard(&opti->pool, 0); > > + string_list_clear(&opti->conflicted_submodules, 1); > + > /* Clean out callback_data as well. */ > FREE_AND_NULL(renames->callback_data); > renames->callback_data_nr = renames->callback_data_alloc = 0; > @@ -1849,6 +1854,17 @@ static int merge_submodule(struct merge_options *opt, Sorry for not catching this in an earlier round, but merge_submodule() has four "return 0" cases, for particular types of conflicts. Those should probably be switched to "goto cleanup" or something like that, so that these messages you are adding are also provided if one of those conflict cases are hit. > object_array_clear(&merges); > cleanup: > + if (!ret) { And here's another item I have to apologize for not catching in an earlier round. We should also require !opt->priv->call_depth as well in this if-condition. If merging of merge bases produced a submodule conflict, but the outer merge involves two sides that resolved the inner conflict the exact same way, then there's no conflict at the outer level and nothing for the user to resolve. If users don't have any conflicts to resolve, we don't want to print messages telling them how to resolve their non-existent conflicts. And if there is still a conflict in the submodule for the outer merge as well as in the recursive merge(s), we don't want to list the module twice (or more) when we tell the user to fix conflicts in their submodules (especially since that means we'd be telling them to merge multiple different commits for the single submodule, which could get confusing). > + struct string_list *csub = &opt->priv->conflicted_submodules; > + char *util; > + const char *abbrev; > + > + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); > + util = xstrdup(abbrev); > + > + string_list_append(csub, path)->util = util; > + } > + > repo_clear(&subrepo); > return ret; > } > @@ -4412,6 +4428,47 @@ static int record_conflicted_index_entries(struct merge_options *opt) > return errs; > } > > +static void print_submodule_conflict_suggestion(struct string_list *csub) { > + int first = 1; > + if (csub->nr > 0) { > + struct string_list_item *item; > + printf(_("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")); > + > + for_each_string_list_item(item, csub) { > + const char *abbrev= item->util; Messed up indent here? > + /* > + * TRANSLATORS: This is a line of advice to resolve a merge conflict > + * in a submodule. The second argument is the abbreviated id of the > + * commit that needs to be merged. > + * E.g. - go to submodule (sub), and either merge commit abc1234" > + */ > + printf(_(" - go to submodule (%s), and either merge commit %s\n" > + " or update to an existing commit which has merged those changes\n"), Indent may be wrong here too, at least if you're trying to get the leftmost quote marks to align. (Maybe the non-alignment was intentional, it was just unclear because of the earlier strange indent.) > + item->string, abbrev); > + } > + printf(_(" - come back to superproject and run:\n\n")); > + for_each_string_list_item(item, csub) > + /* > + * TRANSLATORS: This is a line of a recommended `git add` command > + * with multiple lines of submodule folders. > + * E.g.: git add sub \ > + * sub2 \ > + * sub3 Why does such a message need to be translated? It's literal text the user should type, right? I'm not sure what a translator would do with the message other than regurgitate it. > + */ > + if (first) { > + printf(" git add %s", item->string); But if you did mean for there to be a translation and a TRANSLATORS note, then did you forget to translate it by calling _()? > + first = 0; > + } else { > + printf(" \\\n %s", item->string); > + } Can we put braces around this for_each_string_list_item() block? Or, as an alternative to the whole block, do you want to consider: strub strbuf tmp = STRBUF_INIT; strbuf_add_separated_string_list(&tmp, ' ', csub); printf(_(" git add %s"), tmp.buf); /* or maybe remove the translation; not sure what the point is */ strbuf_release(&tmp); ? It is likely easier to copy & paste, and might be understood by more users (I'm not sure how many are aware that command lines can use backslashes for line continuation), but on the negative side, if you have a lot of submodules it might make it harder to read. Even if you don't like space separated, though, you could still use this strategy by changing the second line to strbuf_add_separated_string_list(&tmp, " \\\n ", csub); > + printf(_("\n\n to record the above merge or update\n" > + " - resolve any other conflicts in the superproject\n" > + " - commit the resulting index in the superproject\n")); > + } > +} > + > void merge_display_update_messages(struct merge_options *opt, > int detailed, > struct merge_result *result) > @@ -4461,6 +4518,9 @@ void merge_display_update_messages(struct merge_options *opt, > } > string_list_clear(&olist, 0); > > + print_submodule_conflict_suggestion(&opti->conflicted_submodules); > + string_list_clear(&opti->conflicted_submodules, 1); > + It would be more consistent to have things allocated in merge_start() continue to be cleared out in clear_or_reinit_internal_opts(). This kind of breaks that pairing, and you're already making sure to clear it there, so I'd rather remove this duplicate string_list_clear() call. > /* Also include needed rename limit adjustment now */ > diff_warn_rename_limit("merge.renamelimit", > opti->renames.needed_limit, 0); > @@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) > trace2_region_enter("merge", "allocate/init", opt->repo); > if (opt->priv) { > clear_or_reinit_internal_opts(opt->priv, 1); > + string_list_init_dup(&opt->priv->conflicted_submodules); This works, but there is a minor optimization available here if you're interested (I understand if you're not since you're already at v6). Assuming you make the important opt->priv->call_depth fix, you can replace string_list_init_dup() with string_list_init_nodup() here. The paths aren't freed until clear_or_reinit_internal_opts() which (under the assumption previously stated) isn't called until merge_finalize(), which comes after the call to merge_display_update_messages(), which is where you use the data. (As repository paths are used all over merge-ort.c, the optimization to store them in one place (opt->priv->paths) and avoid duplicating them is used pretty heavily. It's more important for a lot of the other strmaps since they'll have a lot more paths in them, but it is kind of nice to use this optimization where possible.) > trace2_region_leave("merge", "allocate/init", opt->repo); > return; > } > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index c253bf759a..84913525cf 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' > echo "file-c" > file-c && > git add file-c && > git commit -m "sub-c") && > - git commit -a -m "c" && > + git commit -a -m "c") > +' > > +test_expect_success 'merging should conflict for non fast-forward' ' > + test_when_finished "git -C merge-search reset --hard" && > + (cd merge-search && > + git checkout -b test-nonforward-a b && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > + else > + test_must_fail git merge c 2> actual > + fi) > +' > + > +test_expect_success 'finish setup for merge-search' ' > + (cd merge-search && > git checkout -b d a && > (cd sub && > git checkout -b sub-d sub-b && > @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' > test_cmp expect actual) > ' > > -test_expect_success 'merging should conflict for non fast-forward' ' > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' > (cd merge-search && > - git checkout -b test-nonforward b && > + git checkout -b test-nonforward-b b && > (cd sub && > git rev-parse --short sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > ) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' > git commit -a -m "f" && > > git checkout -b test-backward e && > - test_must_fail git merge f) > + test_must_fail git merge f >actual && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > + grep "$sub_expect" actual > + fi) > ' > > > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh > index 8e32f19007..ebeca12a71 100755 > --- a/t/t7402-submodule-rebase.sh > +++ b/t/t7402-submodule-rebase.sh > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' > test_tick && > git commit -m fourth && > > - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && > + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && > git ls-files -s submodule >actual && > ( > cd submodule && > @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' > echo "160000 $(git rev-parse HEAD^^) 2 submodule" && > echo "160000 $(git rev-parse HEAD) 3 submodule" > ) >expect && > - test_cmp expect actual > + test_cmp expect actual && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && > + grep "$sub_expect" actual_output > + fi > ' > > test_done > > base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a > -- > 2.37.1.359.gd136c6c3e2-goog ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v6] submodule merge: update conflict error message 2022-07-27 1:13 ` Elijah Newren @ 2022-07-27 22:00 ` Calvin Wan 2022-07-28 0:41 ` Elijah Newren 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-07-27 22:00 UTC (permalink / raw) To: Elijah Newren Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð > Well explained. One very minor suggestion: perhaps change "id of the > commit" to "id of the submodule commit" just to make it slightly > clearer that this information would take work for the user to discover > on their own? (When I first read it, I was thinking, "but they have > the commit, it's what they passed to merge", before I realized my > error.) ack > Sorry for not catching this in an earlier round, but merge_submodule() > has four "return 0" cases, for particular types of conflicts. Those > should probably be switched to "goto cleanup" or something like that, > so that these messages you are adding are also provided if one of > those conflict cases are hit. I didn't send these four "return 0" cases to cleanup because I thought the error message wouldn't accurately reflect the resolution steps. Is merging or updating the submodule still the correct resolution? The first three cases are for a null o/a/b, and the fourth case is for a missing local submodule. Also in cleanup, the subrepo is cleared but the subrepo hasn't been initialized/failed to initialize in these four cases. > > object_array_clear(&merges); > > cleanup: > > + if (!ret) { > > And here's another item I have to apologize for not catching in an > earlier round. We should also require !opt->priv->call_depth as well > in this if-condition. If merging of merge bases produced a submodule > conflict, but the outer merge involves two sides that resolved the > inner conflict the exact same way, then there's no conflict at the > outer level and nothing for the user to resolve. If users don't have > any conflicts to resolve, we don't want to print messages telling them > how to resolve their non-existent conflicts. And if there is still a > conflict in the submodule for the outer merge as well as in the > recursive merge(s), we don't want to list the module twice (or more) > when we tell the user to fix conflicts in their submodules (especially > since that means we'd be telling them to merge multiple different > commits for the single submodule, which could get confusing). ack. > > + for_each_string_list_item(item, csub) { > > + const char *abbrev= item->util; > > Messed up indent here? Looks like going from my editor to `git format-patch` messed something up here. > > + for_each_string_list_item(item, csub) > > + /* > > + * TRANSLATORS: This is a line of a recommended `git add` command > > + * with multiple lines of submodule folders. > > + * E.g.: git add sub \ > > + * sub2 \ > > + * sub3 > > Why does such a message need to be translated? It's literal text the > user should type, right? I'm not sure what a translator would do with > the message other than regurgitate it. It doesn't. My point was to let the translator know that the only text in this print is for a git command. I should probably add that context to the comment though. > > + */ > > + if (first) { > > + printf(" git add %s", item->string); > > But if you did mean for there to be a translation and a TRANSLATORS > note, then did you forget to translate it by calling _()? Same reasoning as above. > > + first = 0; > > + } else { > > + printf(" \\\n %s", item->string); > > + } > > Can we put braces around this for_each_string_list_item() block? Or, > as an alternative to the whole block, do you want to consider: > > strub strbuf tmp = STRBUF_INIT; > strbuf_add_separated_string_list(&tmp, ' ', csub); > printf(_(" git add %s"), tmp.buf); /* or maybe remove the > translation; not sure what the point is */ > strbuf_release(&tmp); > ? It is likely easier to copy & paste, and might be understood by > more users (I'm not sure how many are aware that command lines can use > backslashes for line continuation), but on the negative side, if you > have a lot of submodules it might make it harder to read. Even if you > don't like space separated, though, you could still use this strategy > by changing the second line to > > strbuf_add_separated_string_list(&tmp, " \\\n ", csub); This is a much cleaner implementation, thanks! If my goal is to make submodule merging easier for newer submodule users, then I think it's a good assumption to remove any additional possible points of confusion, aka with the "command lines can use backslashes for line continuation", so I'll swap over to spaces. > > + print_submodule_conflict_suggestion(&opti->conflicted_submodules); > > + string_list_clear(&opti->conflicted_submodules, 1); > > + > > It would be more consistent to have things allocated in merge_start() > continue to be cleared out in clear_or_reinit_internal_opts(). This > kind of breaks that pairing, and you're already making sure to clear > it there, so I'd rather remove this duplicate string_list_clear() > call. ack. > > /* Also include needed rename limit adjustment now */ > > diff_warn_rename_limit("merge.renamelimit", > > opti->renames.needed_limit, 0); > > @@ -4657,6 +4717,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) > > trace2_region_enter("merge", "allocate/init", opt->repo); > > if (opt->priv) { > > clear_or_reinit_internal_opts(opt->priv, 1); > > + string_list_init_dup(&opt->priv->conflicted_submodules); > > This works, but there is a minor optimization available here if you're > interested (I understand if you're not since you're already at v6). > Assuming you make the important opt->priv->call_depth fix, you can > replace string_list_init_dup() with string_list_init_nodup() here. > The paths aren't freed until clear_or_reinit_internal_opts() which > (under the assumption previously stated) isn't called until > merge_finalize(), which comes after the call to > merge_display_update_messages(), which is where you use the data. > > (As repository paths are used all over merge-ort.c, the optimization > to store them in one place (opt->priv->paths) and avoid duplicating > them is used pretty heavily. It's more important for a lot of the > other strmaps since they'll have a lot more paths in them, but it is > kind of nice to use this optimization where possible.) Thanks for all the context of this one. I agree it's minor, but any place we can provide a better example to future contributors seems like a more than worthy reason to make this change :) ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v6] submodule merge: update conflict error message 2022-07-27 22:00 ` Calvin Wan @ 2022-07-28 0:41 ` Elijah Newren 2022-07-28 19:06 ` Calvin Wan 0 siblings, 1 reply; 60+ messages in thread From: Elijah Newren @ 2022-07-28 0:41 UTC (permalink / raw) To: Calvin Wan Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð Hi, On Wed, Jul 27, 2022 at 3:00 PM Calvin Wan <calvinwan@google.com> wrote: [...] > > Sorry for not catching this in an earlier round, but merge_submodule() > > has four "return 0" cases, for particular types of conflicts. Those > > should probably be switched to "goto cleanup" or something like that, > > so that these messages you are adding are also provided if one of > > those conflict cases are hit. > > I didn't send these four "return 0" cases to cleanup because I thought > the error message wouldn't accurately reflect the resolution steps. Is > merging or updating the submodule still the correct resolution? The > first three cases are for a null o/a/b, and the fourth case is for a missing > local submodule. Also in cleanup, the subrepo is cleared but the > subrepo hasn't been initialized/failed to initialize in these four cases. Ah, I remember we partially discussed this earlier in this thread; sorry for forgetting. For the failed to initialize case, yes we also need a merge -- the submodule is conflicted due to the lack of one. The steps the user needs to take are probably even more involved, though (they also need to initialize the submodule), so perhaps that one should be special cased. The 'a' or 'b' being a null oid is actually dead code, as discussed earlier in the thread. Perhaps we should change those two code paths from "return 0" to 'BUG("submodule deleted on one side; this should be handled outside of merge_submodule()")', and possibly with a commit message linking to https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/ (and mentioning the "a and b being null oids within merge_submodule will never trigger" portion of that email). The 'o' being a null oid is not dead code. That particular case means that there was no submodule in the merge base, but both sides of the merge introduced the submodule and have it checked out to different oids. (At least, hopefully it's the same submodule.) In that case, yes we do need some kind of merge. So I think your message should probably be included in this case, as-is. Since the cleanup thing you mention is an issue, perhaps you need to refactor the code a bit so that you can make this case somehow get the same message printed for users? All that said, if you want to defer any or all of this to a follow-on series, that's fine...but it would be nice to have it mentioned in the commit message. > > > + for_each_string_list_item(item, csub) > > > + /* > > > + * TRANSLATORS: This is a line of a recommended `git add` command > > > + * with multiple lines of submodule folders. > > > + * E.g.: git add sub \ > > > + * sub2 \ > > > + * sub3 > > > > Why does such a message need to be translated? It's literal text the > > user should type, right? I'm not sure what a translator would do with > > the message other than regurgitate it. > > It doesn't. My point was to let the translator know that the only text > in this print is for a git command. I should probably add that context > to the comment though. Um...if the string doesn't need to be marked for translation, and isn't marked for translation, why would translators go looking for a comment to help explain how to translate something that doesn't appear in their list of strings they need to translate? Using printf(" git add %s", ...) means that the string " git add %s" will not appear in the po/*.po files. If it had been printf(_(" git add %s"), ...) then it would appear in those files with filename(s) and line number(s) stating where the string had come from in case translators needed to look for clues about the context in order to know how to translate the string. So, I think you can just drop the comment. Or am I still not understanding some nuance here? ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v6] submodule merge: update conflict error message 2022-07-28 0:41 ` Elijah Newren @ 2022-07-28 19:06 ` Calvin Wan 0 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-28 19:06 UTC (permalink / raw) To: Elijah Newren Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð > For the failed to initialize case, yes we also need a merge -- the > submodule is conflicted due to the lack of one. The steps the user > needs to take are probably even more involved, though (they also need > to initialize the submodule), so perhaps that one should be special > cased. Adding a needswork bit to this one. Don't quite have the bandwidth to figure out what would be a useful message in this case. > The 'a' or 'b' being a null oid is actually dead code, as discussed > earlier in the thread. Perhaps we should change those two code paths > from "return 0" to 'BUG("submodule deleted on one side; this should be > handled outside of merge_submodule()")', and possibly with a commit > message linking to > https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/ > (and mentioning the "a and b being null oids within merge_submodule > will never trigger" portion of that email). > > The 'o' being a null oid is not dead code. That particular case means > that there was no submodule in the merge base, but both sides of the > merge introduced the submodule and have it checked out to different > oids. (At least, hopefully it's the same submodule.) In that case, > yes we do need some kind of merge. So I think your message should > probably be included in this case, as-is. Since the cleanup thing you > mention is an issue, perhaps you need to refactor the code a bit so > that you can make this case somehow get the same message printed for > users? > > All that said, if you want to defer any or all of this to a follow-on > series, that's fine...but it would be nice to have it mentioned in the > commit message. BUGging out for 'a' and 'b' sounds reasonable to me. And I also do believe my error message applies to the 'o' case. I'll add a test to confirm. > Um...if the string doesn't need to be marked for translation, and > isn't marked for translation, why would translators go looking for a > comment to help explain how to translate something that doesn't appear > in their list of strings they need to translate? Ahhhhhhhhh I see...facepalm > Using > printf(" git add %s", ...) > means that the string " git add %s" will not appear in the po/*.po > files. If it had been > printf(_(" git add %s"), ...) > then it would appear in those files with filename(s) and line > number(s) stating where the string had come from in case translators > needed to look for clues about the context in order to know how to > translate the string. > > So, I think you can just drop the comment. Or am I still not > understanding some nuance here? You're not missing anything. Wasn't aware of how translators worked with the project. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v6] submodule merge: update conflict error message 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan 2022-07-27 1:13 ` Elijah Newren @ 2022-07-27 9:20 ` Ævar Arnfjörð Bjarmason 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan 2 siblings, 0 replies; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-07-27 9:20 UTC (permalink / raw) To: Calvin Wan; +Cc: git, gitster, newren, Johannes.Schindelin On Tue, Jul 26 2022, Calvin Wan wrote: Aside from what Elijah pointed out already... > + > + /* field that holds submodule conflict information */ > + struct string_list conflicted_submodules; Looks good! > cleanup: > + if (!ret) { > + struct string_list *csub = &opt->priv->conflicted_submodules; > + char *util; > + const char *abbrev; > + > + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); > + util = xstrdup(abbrev); > + > + string_list_append(csub, path)->util = util; Elijah pointed out that these don't need to be dup'd at all, and you should follow that advice. I'm not really familiar at all with this code (compared to him). FWIW the "util" here could be dropped in any case, in my version it was because we were making a struct, but the idiom for this would just be: string_list_append(....)->util = xstrdup(...); > + printf(_(" - go to submodule (%s), and either merge commit %s\n" > + " or update to an existing commit which has merged those changes\n"), > + item->string, abbrev); FWIW what I mentioned in v5 was to arrange this so that " - " or whatever would be _()'d separately, so translators wouldn't need to worry about the formatting... > + printf(" git add %s", item->string); > + first = 0; > + } else { > + printf(" \\\n %s", item->string); And if we're translating *some* whitespace we should translate all of it. In RTL languages the a string like " foo" needs to be translated as "foo ". I.e. the whitespace from the "right" side of your terminal. That was what I was pointing out in the object-name.c code, usage_with_options_internal() is another example. > + } > + printf(_("\n\n to record the above merge or update\n" We can add \n\n unconditionally, no need to put it in the translation. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v7] submodule merge: update conflict error message 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan 2022-07-27 1:13 ` Elijah Newren 2022-07-27 9:20 ` Ævar Arnfjörð Bjarmason @ 2022-07-28 21:12 ` Calvin Wan 2022-07-28 23:22 ` Ævar Arnfjörð Bjarmason ` (4 more replies) 2 siblings, 5 replies; 60+ messages in thread From: Calvin Wan @ 2022-07-28 21:12 UTC (permalink / raw) To: git; +Cc: Calvin Wan, gitster, newren, Johannes.Schindelin, avarab When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded or trivially resolved, the merge fails and Git prints an error message that accurately describes the failure, but does not provide steps for the user to resolve the error. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to provide steps to resolve submodule merge conflict. Future work could involve adding an advice flag to the message. Although the message is long, it also has the id of the submodule commit that needs to be merged, which could be useful information for the user. Additionally, 4 merge failures that resulted in an early return have been updated to reflect the status of the merge. 1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added as a new conflict type and will print updated error message. 2. Null merge side a (null a): BUG(). See [1] for discussion 3. Null merge side b (null b): BUG(). See [1] for discussion 4. Submodule not checked out: Still returns early, but added a NEEDSWORK bit since current error message does not reflect the correct resolution [1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/ Signed-off-by: Calvin Wan <calvinwan@google.com> --- For Elijah: Cleaned up the small nits and updated resolutions for those 4 cases we discussed. For Ævar: Apologies for misunderstanding your suggestions to make my messages easier for translators to work with. I have reformatted all of the messages to separate text vs formatting translations. Let me know if this is what you were expecting. merge-ort.c | 112 ++++++++++++++++++++++++++++++++++-- t/t6437-submodule-merge.sh | 78 +++++++++++++++++++++++-- t/t7402-submodule-rebase.sh | 9 ++- 3 files changed, 185 insertions(+), 14 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 01f150ef3b..4302e900ee 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -387,6 +387,9 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct string_list conflicted_submodules; }; struct version_info { @@ -517,6 +520,7 @@ enum conflict_and_info_types { CONFLICT_SUBMODULE_NOT_INITIALIZED, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, + CONFLICT_SUBMODULE_NULL_MERGE_BASE, /* Keep this entry _last_ in the list */ NB_CONFLICT_TYPES, @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = { "CONFLICT (submodule history not available)", [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = "CONFLICT (submodule may have rewinds)", + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = + "CONFLICT (submodule no merge base)" }; struct logical_conflict_info { @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, mem_pool_discard(&opti->pool, 0); + string_list_clear(&opti->conflicted_submodules, 1); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); renames->callback_data_nr = renames->callback_data_alloc = 0; @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt, int i; int search = !opt->priv->call_depth; + int sub_initialized = 1; /* store fallback answer in result in case we fail */ oidcpy(result, opt->priv->call_depth ? o : a); /* we can not handle deletion conflicts */ - if (is_null_oid(o)) - return 0; if (is_null_oid(a)) - return 0; + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); if (is_null_oid(b)) - return 0; + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { + if ((sub_initialized = repo_submodule_init(&subrepo, + opt->repo, path, null_oid()))) { path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s (not checked out)"), path); + /* + * NEEDSWORK: Since the steps to resolve this error are + * more involved than what is currently in + * print_submodule_conflict_suggestion(), we return + * immediately rather than generating an error message + */ return 0; } + if (is_null_oid(o)) { + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (no merge base)"), + path); + goto cleanup; + } + if (!(commit_o = lookup_commit_reference(&subrepo, o)) || !(commit_a = lookup_commit_reference(&subrepo, a)) || !(commit_b = lookup_commit_reference(&subrepo, b))) { @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); cleanup: - repo_clear(&subrepo); + if (!opt->priv->call_depth && !ret) { + struct string_list *csub = &opt->priv->conflicted_submodules; + + string_list_append(csub, path)->util = + xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); + } + + if (!sub_initialized) + repo_clear(&subrepo); return ret; } @@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void print_submodule_conflict_suggestion(struct string_list *csub) { + if (csub->nr > 0) { + struct string_list_item *item; + struct strbuf msg = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + + strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); + strbuf_addf(&msg, "%s\n", tmp.buf); + strbuf_release(&tmp); + + strbuf_addf(&tmp, _("Please manually handle the merging of each conflicted submodule.")); + strbuf_addf(&msg, "%s\n", tmp.buf); + strbuf_release(&tmp); + + strbuf_addf(&tmp, _("This can be accomplished with the following steps:")); + strbuf_addf(&msg, "%s\n", tmp.buf); + strbuf_release(&tmp); + + for_each_string_list_item(item, csub) { + const char *abbrev= item->util; + /* + * TRANSLATORS: This is a line of advice to resolve a merge conflict + * in a submodule. The second argument is the abbreviated id of the + * commit that needs to be merged. + * E.g. - go to submodule (sub), and either merge commit abc1234" + */ + strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s"), + item->string, abbrev); + strbuf_addf(&msg, _(" - %s"), tmp.buf); + strbuf_addf(&msg, "\n"); + strbuf_release(&tmp); + strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); + strbuf_addf(&msg, _(" %s"), tmp.buf); + strbuf_addf(&msg, "\n"); + strbuf_release(&tmp); + } + strbuf_addf(&tmp, _("come back to superproject and run:")); + strbuf_addf(&msg, _(" - %s"), tmp.buf); + strbuf_addf(&msg, "\n\n"); + strbuf_release(&tmp); + + strbuf_addf(&tmp, "git add "); + strbuf_add_separated_string_list(&tmp, " ", csub); + strbuf_addf(&msg, _(" %s"), tmp.buf); + strbuf_addf(&msg, "\n\n"); + strbuf_release(&tmp); + + strbuf_addf(&tmp, _("to record the above merge or update")); + strbuf_addf(&msg, _(" %s"), tmp.buf); + strbuf_addf(&msg, "\n"); + strbuf_release(&tmp); + + strbuf_addf(&tmp, _("resolve any other conflicts in the superproject")); + strbuf_addf(&msg, _(" - %s"), tmp.buf); + strbuf_addf(&msg, "\n"); + strbuf_release(&tmp); + + strbuf_addf(&tmp, _("commit the resulting index in the superproject")); + strbuf_addf(&msg, _(" - %s"), tmp.buf); + strbuf_addf(&msg, "\n"); + strbuf_release(&tmp); + + printf("%s", msg.buf); + strbuf_release(&msg); + } +} + void merge_display_update_messages(struct merge_options *opt, int detailed, struct merge_result *result) @@ -4461,6 +4558,8 @@ void merge_display_update_messages(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); @@ -4657,6 +4756,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_enter("merge", "allocate/init", opt->repo); if (opt->priv) { clear_or_reinit_internal_opts(opt->priv, 1); + string_list_init_nodup(&opt->priv->conflicted_submodules); trace2_region_leave("merge", "allocate/init", opt->repo); return; } diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c253bf759a..414597a420 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' +test_expect_success 'merging should conflict for non fast-forward' ' + test_when_finished "git -C merge-search reset --hard" && + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + else + test_must_fail git merge c 2> actual + fi) +' + +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' @@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward ) ' +# Setup: +# - Submodule has 2 commits: a and b +# - Superproject branch 'a' adds and commits submodule pointing to 'commit a' +# - Superproject branch 'b' adds and commits submodule pointing to 'commit b' +# If these two branches are now merged, there is no merge base +test_expect_success 'setup for null merge base' ' + mkdir no-merge-base && + (cd no-merge-base && + git init && + mkdir sub && + (cd sub && + git init && + echo "file-a" > file-a && + git add file-a && + git commit -m "commit a") && + git commit --allow-empty -m init && + git branch init && + git checkout -b a init && + git add sub && + git commit -m "a" && + git switch main && + (cd sub && + echo "file-b" > file-b && + git add file-b && + git commit -m "commit b")) +' + +test_expect_success 'merging should fail with no merge base' ' + (cd no-merge-base && + git checkout -b b init && + git add sub && + git commit -m "b" && + test_must_fail git merge a >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && + grep "$sub_expect" actual + fi) +' + test_done diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..ebeca12a71 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output + fi ' test_done base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a -- 2.37.1.455.g008518b4e5-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan @ 2022-07-28 23:22 ` Ævar Arnfjörð Bjarmason 2022-07-29 0:24 ` Elijah Newren ` (3 subsequent siblings) 4 siblings, 0 replies; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-07-28 23:22 UTC (permalink / raw) To: Calvin Wan; +Cc: git, gitster, newren, Johannes.Schindelin On Thu, Jul 28 2022, Calvin Wan wrote: > For Elijah: Cleaned up the small nits and updated resolutions for > those 4 cases we discussed. > > For Ævar: Apologies for misunderstanding your suggestions to make > my messages easier for translators to work with. I have reformatted > all of the messages to separate text vs formatting translations. Let > me know if this is what you were expecting. Let's take a look, and thanks for sticking with this... > > merge-ort.c | 112 ++++++++++++++++++++++++++++++++++-- > t/t6437-submodule-merge.sh | 78 +++++++++++++++++++++++-- > t/t7402-submodule-rebase.sh | 9 ++- > 3 files changed, 185 insertions(+), 14 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 01f150ef3b..4302e900ee 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -387,6 +387,9 @@ struct merge_options_internal { > > /* call_depth: recursion level counter for merging merge bases */ > int call_depth; > + > + /* field that holds submodule conflict information */ > + struct string_list conflicted_submodules; > }; > > struct version_info { > @@ -517,6 +520,7 @@ enum conflict_and_info_types { > CONFLICT_SUBMODULE_NOT_INITIALIZED, > CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, > CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, > + CONFLICT_SUBMODULE_NULL_MERGE_BASE, > > /* Keep this entry _last_ in the list */ > NB_CONFLICT_TYPES, > @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = { > "CONFLICT (submodule history not available)", > [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = > "CONFLICT (submodule may have rewinds)", > + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = > + "CONFLICT (submodule no merge base)" > }; > > struct logical_conflict_info { > @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, > > mem_pool_discard(&opti->pool, 0); > > + string_list_clear(&opti->conflicted_submodules, 1); > + > /* Clean out callback_data as well. */ > FREE_AND_NULL(renames->callback_data); > renames->callback_data_nr = renames->callback_data_alloc = 0; > @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt, > > int i; > int search = !opt->priv->call_depth; > + int sub_initialized = 1; > > /* store fallback answer in result in case we fail */ > oidcpy(result, opt->priv->call_depth ? o : a); > > /* we can not handle deletion conflicts */ > - if (is_null_oid(o)) > - return 0; > if (is_null_oid(a)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); > if (is_null_oid(b)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); > > - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { > + if ((sub_initialized = repo_submodule_init(&subrepo, > + opt->repo, path, null_oid()))) { > path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, > path, NULL, NULL, NULL, > _("Failed to merge submodule %s (not checked out)"), > path); > + /* > + * NEEDSWORK: Since the steps to resolve this error are > + * more involved than what is currently in > + * print_submodule_conflict_suggestion(), we return > + * immediately rather than generating an error message > + */ > return 0; > } > > + if (is_null_oid(o)) { > + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, > + path, NULL, NULL, NULL, > + _("Failed to merge submodule %s (no merge base)"), > + path); > + goto cleanup; > + } > + > if (!(commit_o = lookup_commit_reference(&subrepo, o)) || > !(commit_a = lookup_commit_reference(&subrepo, a)) || > !(commit_b = lookup_commit_reference(&subrepo, b))) { > @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt, > > object_array_clear(&merges); > cleanup: > - repo_clear(&subrepo); > + if (!opt->priv->call_depth && !ret) { > + struct string_list *csub = &opt->priv->conflicted_submodules; > + > + string_list_append(csub, path)->util = > + xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); FWIW (since you may have changed this due to my comment) I meant (but maybe didn't make clear enough) in https://lore.kernel.org/git/220727.86ilnilxfv.gmgdl@evledraar.gmail.com/ that just getting rid of the "util" variable could trivially be done, so: struct string_list *csub = &opt->priv->conflicted_submodules; const char *abbrev; abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); string_list_append(csub, path)->util = xstrdup(abbrev); What you have here is also just fine, but in case you traded the line variable for line wrapping I think it's just fine (and actually preferable) to just make an intermediate variable to avoid this sort of line wrapping. Anyway, just clarifying... > +static void print_submodule_conflict_suggestion(struct string_list *csub) { > + if (csub->nr > 0) { Since the entire body of the function is guarded by this maybe avoid the indentation and: if (!csub->nr) return; > + struct string_list_item *item; > + struct strbuf msg = STRBUF_INIT; > + struct strbuf tmp = STRBUF_INIT; > + > + strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("Please manually handle the merging of each conflicted submodule.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("This can be accomplished with the following steps:")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); This was *almost* correct in your v6, i.e.: printf(_("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")); What I meant with "We can add \n\n unconditionally, no need to put it in the translation." in https://lore.kernel.org/git/220727.86ilnilxfv.gmgdl@evledraar.gmail.com/ is that you should avoid adding formatting to translations themselves if possible. i.e. what we're translating here is a full paragraph, so to a translator it doesn't matter if it ends with ":\n" or ":", so we can add the last "\n" unconditionalyl. But we should *not* add the \n's within a single paragraph unconditionally, a translator needs to be able to translate that entire string. Different languages split sentences differently, and different conventions in different languages & SVO v.s. OVS or wahtever (see https://en.wikipedia.org/wiki/Word_order) means that your last sentence might need to come first in some languages. So I think this should just be: printf(_("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:")); putchar('\n'); I.e. the "\n" within the pargraph are imporant, and translators need to be able to amend those, and perhaps some languages will have 2x, some 4x or whatever. Whereas the "\n" at the end is something we can always add, because it's just a matter of how we're interpolating the paragraph into other output (think *nix terminal v.s. say HTML, just as an example). > + > + for_each_string_list_item(item, csub) { > + const char *abbrev= item->util; > + /* > + * TRANSLATORS: This is a line of advice to resolve a merge conflict > + * in a submodule. The second argument is the abbreviated id of the > + * commit that needs to be merged. > + * E.g. - go to submodule (sub), and either merge commit abc1234" > + */ > + strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s"), > + item->string, abbrev); ... > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); Similarly this is worse, because we're now splitting up up a single sentence or paragraph into multiple translations. FWIW I meant that you could write some helper function that would do something like this (in Perl, too lazy to write C now): perl -wE ' my @lines = split /\n/, $ARGV[0]; for (my $i = 0; $i < @lines; $i++) { my $pfx = !$i ? "- " : " "; say "$pfx$lines[$i]"; }' "$(printf "some multi\nline paragraph\nhere")" - some multi line paragraph here I.e. the translator would see: _("some multi\nline paragraph\nhere") Which would allow them to insert any amount of newlines, but you'd just have a utility function that: * Splits those lines by \n * Formats the first line by _("- %s\n") * Formats subsequent lines with _(" %s\n") * The %s in those is a _()'d string. I.e. what's happening at the bottom of show_ambiguous_object(). It's: * A relatively small amount of programming, * Lets translators focus only on these formatting questions for the translations that do the magic formatting, and those only need to be changed for RTL languages. I.e. German and English whill both want "- %s\n", but e.g. Hebrew will want "%s -\n". * Makes the code easier to read, because instead of adding formatting concerns to every string, you'd just: strbuf_addstr(&buf, add_fmt_list_item(i, _("blah bla\nblah blah\nblah.))); > + strbuf_release(&tmp); When you use the strbuf API you should strbuf_reset() within a single scope if you want to "clear" it, not strbuf_release(). The strbuf_release() also works, but then it's a malloc()/free(), each time, with strbuf_reset() we don't free() it, we just set the len to zero, and the content to "\0", but remember how long it was ("alloc" in strbuf.h). You then only do the strbuf_release() at the very end. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan 2022-07-28 23:22 ` Ævar Arnfjörð Bjarmason @ 2022-07-29 0:24 ` Elijah Newren 2022-08-01 22:24 ` Calvin Wan 2022-08-01 12:06 ` Ævar Arnfjörð Bjarmason ` (2 subsequent siblings) 4 siblings, 1 reply; 60+ messages in thread From: Elijah Newren @ 2022-07-29 0:24 UTC (permalink / raw) To: Calvin Wan Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð On Thu, Jul 28, 2022 at 2:12 PM Calvin Wan <calvinwan@google.com> wrote: > > When attempting to merge in a superproject with conflicting submodule > pointers that cannot be fast-forwarded or trivially resolved, the merge > fails and Git prints an error message that accurately describes the > failure, but does not provide steps for the user to resolve the error. > > Git is left in a conflicted state, which requires the user to: > 1. merge submodules or update submodules to an already existing > commit that reflects the merge > 2. add submodules changes to the superproject > 3. finish merging superproject > These steps are non-obvious for newer submodule users to figure out > based on the error message and neither `git submodule status` nor `git > status` provide any useful pointers. > > Update error message to provide steps to resolve submodule merge > conflict. Future work could involve adding an advice flag to the > message. Although the message is long, it also has the id of the > submodule commit that needs to be merged, which could be useful > information for the user. > > Additionally, 4 merge failures that resulted in an early return have > been updated to reflect the status of the merge. > 1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added > as a new conflict type and will print updated error message. > 2. Null merge side a (null a): BUG(). See [1] for discussion > 3. Null merge side b (null b): BUG(). See [1] for discussion > 4. Submodule not checked out: Still returns early, but added a > NEEDSWORK bit since current error message does not reflect the > correct resolution s/does not reflect the correct resolution/also deserves a more detailed explanation of how to resolve/ ? > > [1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/ > > Signed-off-by: Calvin Wan <calvinwan@google.com> > --- > > For Elijah: Cleaned up the small nits and updated resolutions for > those 4 cases we discussed. Thanks. Including a range-diff in the cover letter would be really helpful, for future reference. > > For Ævar: Apologies for misunderstanding your suggestions to make > my messages easier for translators to work with. I have reformatted > all of the messages to separate text vs formatting translations. Let > me know if this is what you were expecting. > > merge-ort.c | 112 ++++++++++++++++++++++++++++++++++-- > t/t6437-submodule-merge.sh | 78 +++++++++++++++++++++++-- > t/t7402-submodule-rebase.sh | 9 ++- > 3 files changed, 185 insertions(+), 14 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 01f150ef3b..4302e900ee 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -387,6 +387,9 @@ struct merge_options_internal { > > /* call_depth: recursion level counter for merging merge bases */ > int call_depth; > + > + /* field that holds submodule conflict information */ > + struct string_list conflicted_submodules; > }; > > struct version_info { > @@ -517,6 +520,7 @@ enum conflict_and_info_types { > CONFLICT_SUBMODULE_NOT_INITIALIZED, > CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, > CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, > + CONFLICT_SUBMODULE_NULL_MERGE_BASE, > > /* Keep this entry _last_ in the list */ > NB_CONFLICT_TYPES, > @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = { > "CONFLICT (submodule history not available)", > [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = > "CONFLICT (submodule may have rewinds)", > + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = > + "CONFLICT (submodule no merge base)" > }; > > struct logical_conflict_info { > @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, > > mem_pool_discard(&opti->pool, 0); > > + string_list_clear(&opti->conflicted_submodules, 1); > + > /* Clean out callback_data as well. */ > FREE_AND_NULL(renames->callback_data); > renames->callback_data_nr = renames->callback_data_alloc = 0; > @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt, > > int i; > int search = !opt->priv->call_depth; > + int sub_initialized = 1; > > /* store fallback answer in result in case we fail */ > oidcpy(result, opt->priv->call_depth ? o : a); > > /* we can not handle deletion conflicts */ > - if (is_null_oid(o)) > - return 0; > if (is_null_oid(a)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); > if (is_null_oid(b)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); > > - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { > + if ((sub_initialized = repo_submodule_init(&subrepo, > + opt->repo, path, null_oid()))) { Um, repo_submodule_init() returns 0 on success, non-zero upon failure. So !sub_initialized means "submodule IS initialized", though it appears to read to mean the opposite. Can you consider renaming your variable (maybe just to "sub_not_initialized")? > path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, > path, NULL, NULL, NULL, > _("Failed to merge submodule %s (not checked out)"), > path); > + /* > + * NEEDSWORK: Since the steps to resolve this error are > + * more involved than what is currently in > + * print_submodule_conflict_suggestion(), we return > + * immediately rather than generating an error message > + */ Technically, we just did generate an error message ("Failed to merge submodule %s (not checked out)"). Maybe replace "immediately rather than generating an error message" with "immediately. It would be better to "goto cleanup" here, after setting some flag requesting a more detailed message be saved for print_submodule_conflict_suggetion()". Or something like that. > return 0; > } > > + if (is_null_oid(o)) { > + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, > + path, NULL, NULL, NULL, > + _("Failed to merge submodule %s (no merge base)"), > + path); > + goto cleanup; > + } Does this need to be moved after initializing the submodule? I thought that was the point of introducing the sub_initialized variable, and we're clearly not going to use it, so it would seem to make more sense to not initialize it for this case. > + > if (!(commit_o = lookup_commit_reference(&subrepo, o)) || > !(commit_a = lookup_commit_reference(&subrepo, a)) || > !(commit_b = lookup_commit_reference(&subrepo, b))) { > @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt, > > object_array_clear(&merges); > cleanup: > - repo_clear(&subrepo); > + if (!opt->priv->call_depth && !ret) { > + struct string_list *csub = &opt->priv->conflicted_submodules; > + > + string_list_append(csub, path)->util = > + xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); > + } > + > + if (!sub_initialized) > + repo_clear(&subrepo); > return ret; > } > > @@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt) > return errs; > } > > +static void print_submodule_conflict_suggestion(struct string_list *csub) { > + if (csub->nr > 0) { > + struct string_list_item *item; > + struct strbuf msg = STRBUF_INIT; > + struct strbuf tmp = STRBUF_INIT; > + > + strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); Instead of these three lines, why not just strbuf_addstr(&msg, "%s\n", _("Recursive merging with submodules currently only supports trivial cases.")); ? Same applies for some of the messages below too. > + strbuf_addf(&tmp, _("Please manually handle the merging of each conflicted submodule.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("This can be accomplished with the following steps:")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + for_each_string_list_item(item, csub) { > + const char *abbrev= item->util; > + /* > + * TRANSLATORS: This is a line of advice to resolve a merge conflict > + * in a submodule. The second argument is the abbreviated id of the > + * commit that needs to be merged. > + * E.g. - go to submodule (sub), and either merge commit abc1234" > + */ > + strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s"), > + item->string, abbrev); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + } > + strbuf_addf(&tmp, _("come back to superproject and run:")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, "git add "); > + strbuf_add_separated_string_list(&tmp, " ", csub); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("to record the above merge or update")); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("resolve any other conflicts in the superproject")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("commit the resulting index in the superproject")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + printf("%s", msg.buf); > + strbuf_release(&msg); Yuck. I'm not a translator, so maybe what you are doing is preferred. But wouldn't translators find it annoying to have to translate " - %s" and " %s" in all these places (and wouldn't there need to be a TRANSLATORS comment before each and every one)? Also, are we running the risk of these short strings needing to be translated differently here than in other places? For example, what if someone is trying to align output nicely: Name: John Johnson Date: 2022-07-28 Gender: Male and uses _(" %s") when printing the name to get the indent right, and expects other languages to add or remove spaces accordingly? Or has other built up messages without hyphens but uses _(" %s") for whatever reason that may not match what you are doing here? If there are multiple _(" %s") in the code, that string can only be translated once. Not once per codepath, but once total. I'm afraid this lego building of messages seems to risk needing translation-per-codepath when that just isn't possible. I also find the big block of code somewhat painful to read. Could we instead do something like (note, I have both a tmp and tmp2): strbuf_add_separated_string_list(&tmp2, " ", csub); for_each_string_list_item(item, csub) { const char *abbrev= item->util; /* * TRANSLATORS: This is a line of advice to resolve a merge conflict * in a submodule. The second argument is the abbreviated id of the * commit that needs to be merged. * E.g. - go to submodule (sub), and either merge commit abc1234 */ strbuf_addf(&tmp, _(" - go to submodule %s, and either merge commit %s\n" " or update to an existing commit which has merged those changes\n"), item->string, abbrev); } strbuf_addf(&msg, _("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" "%s" " - come back to superproject and run:\n\n" " git add %s\n\n" " to record the above merge or update\n" " - resolve any other conflicts in the superproject\n" " - commit the resulting index in the superproject\n"), tmp.buf, tmp2.buf); This will give translators precisely two messages to translate (and we can't drop it to one since one of the two is repeated a variable number of times), and provide more built-in context about how to translate since the whole message is involved. If one of the messages translates into something especially long, they can even add line breaks and reflow the paragraph in ways that make sense for them, which your current version just doesn't permit. > + } > +} > + > void merge_display_update_messages(struct merge_options *opt, > int detailed, > struct merge_result *result) > @@ -4461,6 +4558,8 @@ void merge_display_update_messages(struct merge_options *opt, > } > string_list_clear(&olist, 0); > > + print_submodule_conflict_suggestion(&opti->conflicted_submodules); > + > /* Also include needed rename limit adjustment now */ > diff_warn_rename_limit("merge.renamelimit", > opti->renames.needed_limit, 0); > @@ -4657,6 +4756,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) > trace2_region_enter("merge", "allocate/init", opt->repo); > if (opt->priv) { > clear_or_reinit_internal_opts(opt->priv, 1); > + string_list_init_nodup(&opt->priv->conflicted_submodules); > trace2_region_leave("merge", "allocate/init", opt->repo); > return; > } > diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh > index c253bf759a..414597a420 100755 > --- a/t/t6437-submodule-merge.sh > +++ b/t/t6437-submodule-merge.sh > @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' > echo "file-c" > file-c && > git add file-c && > git commit -m "sub-c") && > - git commit -a -m "c" && > + git commit -a -m "c") > +' > > +test_expect_success 'merging should conflict for non fast-forward' ' > + test_when_finished "git -C merge-search reset --hard" && > + (cd merge-search && > + git checkout -b test-nonforward-a b && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > + else > + test_must_fail git merge c 2> actual > + fi) > +' > + > +test_expect_success 'finish setup for merge-search' ' > + (cd merge-search && > git checkout -b d a && > (cd sub && > git checkout -b sub-d sub-b && > @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' > test_cmp expect actual) > ' > > -test_expect_success 'merging should conflict for non fast-forward' ' > +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' > (cd merge-search && > - git checkout -b test-nonforward b && > + git checkout -b test-nonforward-b b && > (cd sub && > git rev-parse --short sub-d > ../expect) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' > ) && > if test "$GIT_TEST_MERGE_ALGORITHM" = ort > then > - test_must_fail git merge c >actual > + test_must_fail git merge c >actual && > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && > + grep "$sub_expect" actual > else > test_must_fail git merge c 2> actual > fi && > @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' > git commit -a -m "f" && > > git checkout -b test-backward e && > - test_must_fail git merge f) > + test_must_fail git merge f >actual && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && > + grep "$sub_expect" actual > + fi) > ' > > > @@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward > ) > ' > > +# Setup: > +# - Submodule has 2 commits: a and b > +# - Superproject branch 'a' adds and commits submodule pointing to 'commit a' > +# - Superproject branch 'b' adds and commits submodule pointing to 'commit b' > +# If these two branches are now merged, there is no merge base > +test_expect_success 'setup for null merge base' ' > + mkdir no-merge-base && > + (cd no-merge-base && > + git init && > + mkdir sub && > + (cd sub && > + git init && > + echo "file-a" > file-a && > + git add file-a && > + git commit -m "commit a") && > + git commit --allow-empty -m init && > + git branch init && > + git checkout -b a init && > + git add sub && > + git commit -m "a" && > + git switch main && > + (cd sub && > + echo "file-b" > file-b && > + git add file-b && > + git commit -m "commit b")) > +' > + > +test_expect_success 'merging should fail with no merge base' ' > + (cd no-merge-base && > + git checkout -b b init && > + git add sub && > + git commit -m "b" && > + test_must_fail git merge a >actual && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && > + grep "$sub_expect" actual > + fi) > +' > + > test_done > diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh > index 8e32f19007..ebeca12a71 100755 > --- a/t/t7402-submodule-rebase.sh > +++ b/t/t7402-submodule-rebase.sh > @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' > test_tick && > git commit -m fourth && > > - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && > + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && > git ls-files -s submodule >actual && > ( > cd submodule && > @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' > echo "160000 $(git rev-parse HEAD^^) 2 submodule" && > echo "160000 $(git rev-parse HEAD) 3 submodule" > ) >expect && > - test_cmp expect actual > + test_cmp expect actual && > + if test "$GIT_TEST_MERGE_ALGORITHM" = ort > + then > + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && > + grep "$sub_expect" actual_output > + fi > ' > > test_done > > base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a > -- > 2.37.1.455.g008518b4e5-goog > ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-07-29 0:24 ` Elijah Newren @ 2022-08-01 22:24 ` Calvin Wan 0 siblings, 0 replies; 60+ messages in thread From: Calvin Wan @ 2022-08-01 22:24 UTC (permalink / raw) To: Elijah Newren Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð > > 4. Submodule not checked out: Still returns early, but added a > > NEEDSWORK bit since current error message does not reflect the > > correct resolution > > s/does not reflect the correct resolution/also deserves a more > detailed explanation of how to resolve/ ? ack > Thanks. Including a range-diff in the cover letter would be really > helpful, for future reference. will do > Um, repo_submodule_init() returns 0 on success, non-zero upon failure. > So !sub_initialized means "submodule IS initialized", though it > appears to read to mean the opposite. Can you consider renaming your > variable (maybe just to "sub_not_initialized")? ack > Technically, we just did generate an error message ("Failed to merge > submodule %s (not checked out)"). > > Maybe replace "immediately rather than generating an error message" > with "immediately. It would be better to "goto cleanup" here, after > setting some flag requesting a more detailed message be saved for > print_submodule_conflict_suggetion()". Or something like that. Sounds like a better place to put the NEEDSWORK bit. > > > return 0; > > } > > > > + if (is_null_oid(o)) { > > + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, > > + path, NULL, NULL, NULL, > > + _("Failed to merge submodule %s (no merge base)"), > > + path); > > + goto cleanup; > > + } > > Does this need to be moved after initializing the submodule? I > thought that was the point of introducing the sub_initialized > variable, and we're clearly not going to use it, so it would seem to > make more sense to not initialize it for this case. The submodule needs to be initialized to generate part of the error message. See the following in cleanup: repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV) > Yuck. I'm not a translator, so maybe what you are doing is preferred. > But wouldn't translators find it annoying to have to translate " - %s" > and " %s" in all these places (and wouldn't there need to be a > TRANSLATORS comment before each and every one)? As per Avar's suggestion, I made a helper function so translators only have to translate " - %s" and " %s" once. This change does end up lining up the `git add ...` command, but I think that's fine - come back to superproject and run: git add sub3 sub2 sub to record the above merge or update > I also find the big block of code somewhat painful to read. Could we > instead do something like (note, I have both a tmp and tmp2): > > strbuf_add_separated_string_list(&tmp2, " ", csub); > > for_each_string_list_item(item, csub) { > const char *abbrev= item->util; > /* > * TRANSLATORS: This is a line of advice to resolve a merge conflict > * in a submodule. The second argument is the abbreviated id of the > * commit that needs to be merged. > * E.g. - go to submodule (sub), and either merge commit abc1234 > */ > strbuf_addf(&tmp, _(" - go to submodule %s, and either merge > commit %s\n" > " or update to an existing commit which > has merged those changes\n"), > item->string, abbrev); > } > > strbuf_addf(&msg, > _("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" > "%s" > " - come back to superproject and run:\n\n" > " git add %s\n\n" > " to record the above merge or update\n" > " - resolve any other conflicts in the superproject\n" > " - commit the resulting index in the superproject\n"), > tmp.buf, tmp2.buf); > > This will give translators precisely two messages to translate (and we > can't drop it to one since one of the two is repeated a variable > number of times), and provide more built-in context about how to > translate since the whole message is involved. If one of the messages > translates into something especially long, they can even add line > breaks and reflow the paragraph in ways that make sense for them, > which your current version just doesn't permit. I think Avar's suggestion makes translating the formatting easier than your suggestion, at the cost of having a big block of code to setup it all up. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan 2022-07-28 23:22 ` Ævar Arnfjörð Bjarmason 2022-07-29 0:24 ` Elijah Newren @ 2022-08-01 12:06 ` Ævar Arnfjörð Bjarmason 2022-08-02 0:50 ` Junio C Hamano 2022-08-04 19:51 ` [PATCH v8] " Calvin Wan 4 siblings, 0 replies; 60+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-08-01 12:06 UTC (permalink / raw) To: Calvin Wan; +Cc: git, gitster, newren, Johannes.Schindelin On Thu, Jul 28 2022, Calvin Wan wrote: > + strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); Aside from anything else mentioned, please run the "static-analysis" job in CI (or coccicheck locally) to sanity check patches. The "seen" branch is currently failing on this & similar API use: https://github.com/git/git/runs/7587382291?check_suite_focus=true Per my previous feedback we should change this anyway, but that would have shown that doing this particular thing is better done with strbuf_addbuf() (and actually buggy with strbuf_addf(), if we had a vector where format specifiers could be inserted). ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan ` (2 preceding siblings ...) 2022-08-01 12:06 ` Ævar Arnfjörð Bjarmason @ 2022-08-02 0:50 ` Junio C Hamano 2022-08-02 21:03 ` Calvin Wan 2022-08-04 19:51 ` [PATCH v8] " Calvin Wan 4 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-08-02 0:50 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Calvin Wan <calvinwan@google.com> writes: > merge-ort.c | 112 ++++++++++++++++++++++++++++++++++-- > t/t6437-submodule-merge.sh | 78 +++++++++++++++++++++++-- > t/t7402-submodule-rebase.sh | 9 ++- > 3 files changed, 185 insertions(+), 14 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 01f150ef3b..4302e900ee 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -387,6 +387,9 @@ struct merge_options_internal { > > /* call_depth: recursion level counter for merging merge bases */ > int call_depth; > + > + /* field that holds submodule conflict information */ > + struct string_list conflicted_submodules; > }; > > struct version_info { > @@ -517,6 +520,7 @@ enum conflict_and_info_types { > CONFLICT_SUBMODULE_NOT_INITIALIZED, > CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, > CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, > + CONFLICT_SUBMODULE_NULL_MERGE_BASE, > > /* Keep this entry _last_ in the list */ > NB_CONFLICT_TYPES, > @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = { > "CONFLICT (submodule history not available)", > [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = > "CONFLICT (submodule may have rewinds)", The other ones are semi sentences ... > + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = > + "CONFLICT (submodule no merge base)" ... and this wants to become one, too, e.g. "submodule lacks merge base", perhaps? > }; > @@ -686,6 +692,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, > > mem_pool_discard(&opti->pool, 0); > > + string_list_clear(&opti->conflicted_submodules, 1); > + > /* Clean out callback_data as well. */ > FREE_AND_NULL(renames->callback_data); > renames->callback_data_nr = renames->callback_data_alloc = 0; > @@ -1744,26 +1752,40 @@ static int merge_submodule(struct merge_options *opt, > > int i; > int search = !opt->priv->call_depth; > + int sub_initialized = 1; > > /* store fallback answer in result in case we fail */ > oidcpy(result, opt->priv->call_depth ? o : a); > > /* we can not handle deletion conflicts */ > - if (is_null_oid(o)) > - return 0; > if (is_null_oid(a)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); > if (is_null_oid(b)) > - return 0; > + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); OK. It is interesting that the first condition (i.e. 'o' is missing) we are removing is not about "we cannot handle deletion", so this change corrects the code to match the comment. As the BUG() messages are the same on these sides, if (is_null_oid(a) || is_null_oid(b)) BUG(...); may be easier to read, perhaps? > - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { > + if ((sub_initialized = repo_submodule_init(&subrepo, > + opt->repo, path, null_oid()))) { I wonder where this overly deep indentation come from? Can the .editorconfig file we ship with the project help? > path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, > path, NULL, NULL, NULL, > _("Failed to merge submodule %s (not checked out)"), > path); > + /* > + * NEEDSWORK: Since the steps to resolve this error are > + * more involved than what is currently in > + * print_submodule_conflict_suggestion(), we return > + * immediately rather than generating an error message > + */ OK. > return 0; > } > > + if (is_null_oid(o)) { > + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, > + path, NULL, NULL, NULL, > + _("Failed to merge submodule %s (no merge base)"), > + path); OK. > + goto cleanup; > + } > @@ -1849,7 +1871,15 @@ static int merge_submodule(struct merge_options *opt, > > object_array_clear(&merges); > cleanup: > - repo_clear(&subrepo); > + if (!opt->priv->call_depth && !ret) { > + struct string_list *csub = &opt->priv->conflicted_submodules; > + > + string_list_append(csub, path)->util = > + xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); This line could also lose one level of indent. We record 'b' in its abbreviated form here, because the assumption is that when merging the superproject, 'a' (which is the assumed fallback answer wet up at the beginning of the furnction) is the commit recorded in our side of the superproject, and it is the commit checked out in the submodule, so the task of making the necessary merge in the submodule is to go to the submodule and then merge 'b' into HEAD. Makes sense. > @@ -4412,6 +4442,73 @@ static int record_conflicted_index_entries(struct merge_options *opt) > return errs; > } > > +static void print_submodule_conflict_suggestion(struct string_list *csub) { > + if (csub->nr > 0) { > + struct string_list_item *item; > + struct strbuf msg = STRBUF_INIT; > + struct strbuf tmp = STRBUF_INIT; > + > + strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("Please manually handle the merging of each conflicted submodule.")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("This can be accomplished with the following steps:")); > + strbuf_addf(&msg, "%s\n", tmp.buf); > + strbuf_release(&tmp); > + > + for_each_string_list_item(item, csub) { > + const char *abbrev= item->util; > + /* > + * TRANSLATORS: This is a line of advice to resolve a merge conflict > + * in a submodule. The second argument is the abbreviated id of the > + * commit that needs to be merged. > + * E.g. - go to submodule (sub), and either merge commit abc1234" > + */ > + strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s"), > + item->string, abbrev); Is this also an instance of overly deep indentation (it is so deep that I cannot easily tell)? When we say "either merge commit %s" (where %s is 'b'---what they have as we saw earlier), do we need to mention what the value of 'a' is to the user, or is it redundant because we are absolutely sure that 'a' is what is checked out in the submodule? > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); "those changes" means "a..b"? Again, I just want to make sure that the user in this situation knows "the other end" of the range when they are told only about 'b'. > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + } > + strbuf_addf(&tmp, _("come back to superproject and run:")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, "git add "); > + strbuf_add_separated_string_list(&tmp, " ", csub); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("to record the above merge or update")); > + strbuf_addf(&msg, _(" %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("resolve any other conflicts in the superproject")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + strbuf_addf(&tmp, _("commit the resulting index in the superproject")); > + strbuf_addf(&msg, _(" - %s"), tmp.buf); > + strbuf_addf(&msg, "\n"); > + strbuf_release(&tmp); > + > + printf("%s", msg.buf); > + strbuf_release(&msg); > + } > +} OK. Makes sense. Thanks. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-08-02 0:50 ` Junio C Hamano @ 2022-08-02 21:03 ` Calvin Wan 2022-08-02 21:11 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-08-02 21:03 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, newren, Johannes.Schindelin, avarab > > @@ -570,6 +574,8 @@ static const char *type_short_descriptions[] = { > > "CONFLICT (submodule history not available)", > > [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = > > "CONFLICT (submodule may have rewinds)", > > The other ones are semi sentences ... > > > + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = > > + "CONFLICT (submodule no merge base)" > > ... and this wants to become one, too, e.g. "submodule lacks merge > base", perhaps? lacks/missing both SGTM > if (is_null_oid(a) || is_null_oid(b)) > BUG(...); > > may be easier to read, perhaps? ack > I wonder where this overly deep indentation come from? Can the > .editorconfig file we ship with the project help? It comes from me not properly indenting the line. I'll take a look at the .editorconfig file. > When we say "either merge commit %s" (where %s is 'b'---what they > have as we saw earlier), do we need to mention what the value of 'a' > is to the user, or is it redundant because we are absolutely sure > that 'a' is what is checked out in the submodule? We are absolutely sure that 'a' is the GITLINK to the submodule, but if the user changes their local submodule without updating the reference, it can result in errors like "not checked out" and "commits not present" during the merge. > > + strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); > > "those changes" means "a..b"? Again, I just want to make sure that > the user in this situation knows "the other end" of the range when > they are told only about 'b'. Correct. Based on what I said above, it might be worthwhile to mention 'a' as well since that information could also help the user in those cases. I'm thinking something like this: "go to submodule ('sub' : 'a'), and either merge commit 'b'\n" "go to submodule ('sub', 'a'), and either merge commit 'b'\n" "go to submodule 'sub', commit 'a', and either merge commit 'b'\n" ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-08-02 21:03 ` Calvin Wan @ 2022-08-02 21:11 ` Junio C Hamano 2022-08-02 21:55 ` Calvin Wan 0 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-08-02 21:11 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Calvin Wan <calvinwan@google.com> writes: > I'm thinking something like this: > > "go to submodule ('sub' : 'a'), and either merge commit 'b'\n" > "go to submodule ('sub', 'a'), and either merge commit 'b'\n" > "go to submodule 'sub', commit 'a', and either merge commit 'b'\n" In the first two, I suspect that it may not be quite clear what 'a' means to the user. In the third one, the first "commit" might be mistaken as a verb. I am tempted to say cd to <sub>, run "checkout --detach <a>" then "merge <b>" but that may be a bit too prescriptive. I dunno. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-08-02 21:11 ` Junio C Hamano @ 2022-08-02 21:55 ` Calvin Wan 2022-08-02 22:22 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-08-02 21:55 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, newren, Johannes.Schindelin, avarab I'm starting to think this is getting out of scope for my patch. For the errors, "not checked out" and "commits not present", I will have a NEEDSWORK bit attached to them in print_submodule_conflict(), and if any of the submodules has those errors, then my message won't print out. That way, we are guaranteed to have 'a' checked out when my message prints, rendering it redundant. ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v7] submodule merge: update conflict error message 2022-08-02 21:55 ` Calvin Wan @ 2022-08-02 22:22 ` Junio C Hamano 0 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-08-02 22:22 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Calvin Wan <calvinwan@google.com> writes: > I'm starting to think this is getting out of scope for my patch. > For the errors, "not checked out" and "commits not present", > I will have a NEEDSWORK bit attached to them in > print_submodule_conflict(), and if any of the submodules has > those errors, then my message won't print out. That way, > we are guaranteed to have 'a' checked out when my message > prints, rendering it redundant. That's fine, then. Thanks for thinking it through. ^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v8] submodule merge: update conflict error message 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan ` (3 preceding siblings ...) 2022-08-02 0:50 ` Junio C Hamano @ 2022-08-04 19:51 ` Calvin Wan 2022-08-16 15:58 ` Junio C Hamano 4 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-08-04 19:51 UTC (permalink / raw) To: git; +Cc: Calvin Wan, gitster, newren, Johannes.Schindelin, avarab When attempting to merge in a superproject with conflicting submodule pointers that cannot be fast-forwarded or trivially resolved, the merge fails and Git prints an error message that accurately describes the failure, but does not provide steps for the user to resolve the error. Git is left in a conflicted state, which requires the user to: 1. merge submodules or update submodules to an already existing commit that reflects the merge 2. add submodules changes to the superproject 3. finish merging superproject These steps are non-obvious for newer submodule users to figure out based on the error message and neither `git submodule status` nor `git status` provide any useful pointers. Update error message to provide steps to resolve submodule merge conflict. Future work could involve adding an advice flag to the message. Although the message is long, it also has the id of the submodule commit that needs to be merged, which could be useful information for the user. Additionally, 5 merge failures that resulted in an early return have been updated to reflect the status of the merge. 1. Null merge base (null o): CONFLICT_SUBMODULE_NULL_MERGE_BASE added as a new conflict type and will print updated error message. 2. Null merge side a (null a): BUG(). See [1] for discussion 3. Null merge side b (null b): BUG(). See [1] for discussion 4. Submodule not checked out: added NEEDSWORK bit 5. Submodule commits not present: added NEEDSWORK bit The errors with a NEEDSWORK bit deserve a more detailed explanation of how to resolve them. See [2] for more context. [1] https://lore.kernel.org/git/CABPp-BE0qGwUy80dmVszkJQ+tcpfLRW0OZyErymzhZ9+HWY1mw@mail.gmail.com/ [2] https://lore.kernel.org/git/xmqqpmhjjwo9.fsf@gitster.g/ Signed-off-by: Calvin Wan <calvinwan@google.com> --- Range-diff against v7: 1: 0640133250 ! 1: 24a4652b25 submodule merge: update conflict error message @@ merge-ort.c: struct merge_options_internal { + + /* field that holds submodule conflict information */ + struct string_list conflicted_submodules; ++}; ++ ++struct conflicted_submodule_item { ++ char *abbrev; ++ int flag; }; ++static void conflicted_submodule_item_free(void *util, const char *str) ++{ ++ struct conflicted_submodule_item *item = util; ++ ++ free(item->abbrev); ++ free(item); ++} ++ struct version_info { + struct object_id oid; + unsigned short mode; @@ merge-ort.c: enum conflict_and_info_types { CONFLICT_SUBMODULE_NOT_INITIALIZED, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, @@ merge-ort.c: static const char *type_short_descriptions[] = { [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = "CONFLICT (submodule may have rewinds)", + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = -+ "CONFLICT (submodule no merge base)" ++ "CONFLICT (submodule lacks merge base)" }; struct logical_conflict_info { @@ merge-ort.c: static void clear_or_reinit_internal_opts(struct merge_options_inte mem_pool_discard(&opti->pool, 0); -+ string_list_clear(&opti->conflicted_submodules, 1); ++ string_list_clear_func(&opti->conflicted_submodules, ++ conflicted_submodule_item_free); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); @@ merge-ort.c: static int merge_submodule(struct merge_options *opt, int i; int search = !opt->priv->call_depth; -+ int sub_initialized = 1; ++ int sub_not_initialized = 1; ++ int sub_flag = -1; /* store fallback answer in result in case we fail */ oidcpy(result, opt->priv->call_depth ? o : a); @@ merge-ort.c: static int merge_submodule(struct merge_options *opt, /* we can not handle deletion conflicts */ - if (is_null_oid(o)) - return 0; - if (is_null_oid(a)) +- if (is_null_oid(a)) - return 0; -+ BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); - if (is_null_oid(b)) +- if (is_null_oid(b)) - return 0; ++ if (is_null_oid(a) || is_null_oid(b)) + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { -+ if ((sub_initialized = repo_submodule_init(&subrepo, -+ opt->repo, path, null_oid()))) { ++ if ((sub_not_initialized = repo_submodule_init(&subrepo, ++ opt->repo, path, null_oid()))) { path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s (not checked out)"), path); -+ /* -+ * NEEDSWORK: Since the steps to resolve this error are -+ * more involved than what is currently in -+ * print_submodule_conflict_suggestion(), we return -+ * immediately rather than generating an error message -+ */ - return 0; - } - +- return 0; ++ sub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED; ++ goto cleanup; ++ } ++ + if (is_null_oid(o)) { + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (no merge base)"), + path); + goto cleanup; -+ } -+ + } + if (!(commit_o = lookup_commit_reference(&subrepo, o)) || - !(commit_a = lookup_commit_reference(&subrepo, a)) || - !(commit_b = lookup_commit_reference(&subrepo, b))) { +@@ merge-ort.c: static int merge_submodule(struct merge_options *opt, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (commits not present)"), + path); ++ sub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE; + goto cleanup; + } + @@ merge-ort.c: static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); @@ merge-ort.c: static int merge_submodule(struct merge_options *opt, - repo_clear(&subrepo); + if (!opt->priv->call_depth && !ret) { + struct string_list *csub = &opt->priv->conflicted_submodules; ++ struct conflicted_submodule_item *util; ++ const char *abbrev; + -+ string_list_append(csub, path)->util = -+ xstrdup(repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV)); ++ util = xmalloc(sizeof(*util)); ++ util->flag = sub_flag; ++ util->abbrev = NULL; ++ if (!sub_not_initialized) { ++ abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); ++ util->abbrev = xstrdup(abbrev); ++ } ++ string_list_append(csub, path)->util = util; + } + -+ if (!sub_initialized) ++ if (!sub_not_initialized) + repo_clear(&subrepo); return ret; } @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op return errs; } -+static void print_submodule_conflict_suggestion(struct string_list *csub) { -+ if (csub->nr > 0) { -+ struct string_list_item *item; -+ struct strbuf msg = STRBUF_INIT; -+ struct strbuf tmp = STRBUF_INIT; -+ -+ strbuf_addf(&tmp, _("Recursive merging with submodules currently only supports trivial cases.")); -+ strbuf_addf(&msg, "%s\n", tmp.buf); -+ strbuf_release(&tmp); ++static void format_submodule_conflict_suggestion(struct strbuf *msg) { ++ struct strbuf tmp = STRBUF_INIT; ++ struct string_list msg_list = STRING_LIST_INIT_DUP; ++ int i; + -+ strbuf_addf(&tmp, _("Please manually handle the merging of each conflicted submodule.")); -+ strbuf_addf(&msg, "%s\n", tmp.buf); -+ strbuf_release(&tmp); -+ -+ strbuf_addf(&tmp, _("This can be accomplished with the following steps:")); -+ strbuf_addf(&msg, "%s\n", tmp.buf); -+ strbuf_release(&tmp); -+ -+ for_each_string_list_item(item, csub) { -+ const char *abbrev= item->util; ++ string_list_split(&msg_list, msg->buf, '\n', -1); ++ for (i = 0; i < msg_list.nr; i++) { ++ if (!i) + /* -+ * TRANSLATORS: This is a line of advice to resolve a merge conflict -+ * in a submodule. The second argument is the abbreviated id of the -+ * commit that needs to be merged. -+ * E.g. - go to submodule (sub), and either merge commit abc1234" ++ * TRANSLATORS: This is line item of submodule conflict message ++ * from print_submodule_conflict_suggestion() below. For RTL ++ * languages, the following swap is suggested: ++ * " - %s\n" -> "%s - \n" + */ -+ strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s"), -+ item->string, abbrev); -+ strbuf_addf(&msg, _(" - %s"), tmp.buf); -+ strbuf_addf(&msg, "\n"); -+ strbuf_release(&tmp); -+ strbuf_addf(&tmp, _("or update to an existing commit which has merged those changes")); -+ strbuf_addf(&msg, _(" %s"), tmp.buf); -+ strbuf_addf(&msg, "\n"); -+ strbuf_release(&tmp); -+ } -+ strbuf_addf(&tmp, _("come back to superproject and run:")); -+ strbuf_addf(&msg, _(" - %s"), tmp.buf); -+ strbuf_addf(&msg, "\n\n"); -+ strbuf_release(&tmp); ++ strbuf_addf(&tmp, _(" - %s\n"), msg_list.items[i].string); ++ else ++ /* ++ * TRANSLATORS: This is line item of submodule conflict message ++ * from print_submodule_conflict_suggestion() below. For RTL ++ * languages, the following swap is suggested: ++ * " %s\n" -> "%s \n" ++ */ ++ strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); ++ } ++ strbuf_reset(msg); ++ strbuf_add(msg, tmp.buf, tmp.len); ++} + -+ strbuf_addf(&tmp, "git add "); -+ strbuf_add_separated_string_list(&tmp, " ", csub); -+ strbuf_addf(&msg, _(" %s"), tmp.buf); -+ strbuf_addf(&msg, "\n\n"); -+ strbuf_release(&tmp); ++static void print_submodule_conflict_suggestion(struct string_list *csub) { ++ struct string_list_item *item; ++ struct strbuf msg = STRBUF_INIT; ++ struct strbuf tmp = STRBUF_INIT; ++ struct strbuf subs = STRBUF_INIT; + -+ strbuf_addf(&tmp, _("to record the above merge or update")); -+ strbuf_addf(&msg, _(" %s"), tmp.buf); -+ strbuf_addf(&msg, "\n"); -+ strbuf_release(&tmp); ++ if (!csub->nr) ++ return; ++ ++ /* ++ * NEEDSWORK: The steps to resolve these errors deserve a more ++ * detailed explanation than what is currently printed below. ++ */ ++ for_each_string_list_item(item, csub) { ++ struct conflicted_submodule_item *util = item->util; + -+ strbuf_addf(&tmp, _("resolve any other conflicts in the superproject")); -+ strbuf_addf(&msg, _(" - %s"), tmp.buf); -+ strbuf_addf(&msg, "\n"); -+ strbuf_release(&tmp); ++ if (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED || ++ util->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE) ++ return; ++ } + -+ strbuf_addf(&tmp, _("commit the resulting index in the superproject")); -+ strbuf_addf(&msg, _(" - %s"), tmp.buf); -+ strbuf_addf(&msg, "\n"); -+ strbuf_release(&tmp); ++ printf(_("Recursive 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:")); ++ putchar('\n'); + -+ printf("%s", msg.buf); -+ strbuf_release(&msg); ++ for_each_string_list_item(item, csub) { ++ struct conflicted_submodule_item *util = item->util; ++ /* ++ * TRANSLATORS: This is a line of advice to resolve a merge conflict ++ * in a submodule. The second argument is the abbreviated id of the ++ * commit that needs to be merged. ++ * E.g. - go to submodule (sub), and either merge commit abc1234" ++ */ ++ strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s\n" ++ "or update to an existing commit which has merged those changes"), ++ item->string, util->abbrev); ++ format_submodule_conflict_suggestion(&tmp); ++ strbuf_add(&msg, tmp.buf, tmp.len); ++ strbuf_reset(&tmp); + } ++ strbuf_add_separated_string_list(&subs, " ", csub); ++ strbuf_addstr(&tmp, _("come back to superproject and run:")); ++ strbuf_addf(&tmp, "\n\ngit add %s\n\n", subs.buf); ++ strbuf_addstr(&tmp, _("to record the above merge or update")); ++ format_submodule_conflict_suggestion(&tmp); ++ strbuf_add(&msg, tmp.buf, tmp.len); ++ strbuf_reset(&tmp); ++ ++ strbuf_addstr(&tmp, _("resolve any other conflicts in the superproject")); ++ format_submodule_conflict_suggestion(&tmp); ++ strbuf_add(&msg, tmp.buf, tmp.len); ++ strbuf_reset(&tmp); ++ ++ strbuf_addstr(&tmp, _("commit the resulting index in the superproject")); ++ format_submodule_conflict_suggestion(&tmp); ++ strbuf_add(&msg, tmp.buf, tmp.len); ++ ++ printf("%s", msg.buf); ++ strbuf_release(&subs); ++ strbuf_release(&tmp); ++ strbuf_release(&msg); +} + void merge_display_update_messages(struct merge_options *opt, merge-ort.c | 161 ++++++++++++++++++++++++++++++++++-- t/t6437-submodule-merge.sh | 78 +++++++++++++++-- t/t7402-submodule-rebase.sh | 9 +- 3 files changed, 231 insertions(+), 17 deletions(-) diff --git a/merge-ort.c b/merge-ort.c index 01f150ef3b..86985d5c61 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -387,8 +387,24 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct string_list conflicted_submodules; +}; + +struct conflicted_submodule_item { + char *abbrev; + int flag; }; +static void conflicted_submodule_item_free(void *util, const char *str) +{ + struct conflicted_submodule_item *item = util; + + free(item->abbrev); + free(item); +} + struct version_info { struct object_id oid; unsigned short mode; @@ -517,6 +533,7 @@ enum conflict_and_info_types { CONFLICT_SUBMODULE_NOT_INITIALIZED, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, + CONFLICT_SUBMODULE_NULL_MERGE_BASE, /* Keep this entry _last_ in the list */ NB_CONFLICT_TYPES, @@ -570,6 +587,8 @@ static const char *type_short_descriptions[] = { "CONFLICT (submodule history not available)", [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = "CONFLICT (submodule may have rewinds)", + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = + "CONFLICT (submodule lacks merge base)" }; struct logical_conflict_info { @@ -686,6 +705,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, mem_pool_discard(&opti->pool, 0); + string_list_clear_func(&opti->conflicted_submodules, + conflicted_submodule_item_free); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); renames->callback_data_nr = renames->callback_data_alloc = 0; @@ -1744,24 +1766,32 @@ static int merge_submodule(struct merge_options *opt, int i; int search = !opt->priv->call_depth; + int sub_not_initialized = 1; + int sub_flag = -1; /* store fallback answer in result in case we fail */ oidcpy(result, opt->priv->call_depth ? o : a); /* we can not handle deletion conflicts */ - if (is_null_oid(o)) - return 0; - if (is_null_oid(a)) - return 0; - if (is_null_oid(b)) - return 0; + if (is_null_oid(a) || is_null_oid(b)) + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { + if ((sub_not_initialized = repo_submodule_init(&subrepo, + opt->repo, path, null_oid()))) { path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s (not checked out)"), path); - return 0; + sub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED; + goto cleanup; + } + + if (is_null_oid(o)) { + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (no merge base)"), + path); + goto cleanup; } if (!(commit_o = lookup_commit_reference(&subrepo, o)) || @@ -1771,6 +1801,7 @@ static int merge_submodule(struct merge_options *opt, path, NULL, NULL, NULL, _("Failed to merge submodule %s (commits not present)"), path); + sub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE; goto cleanup; } @@ -1849,7 +1880,23 @@ static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); cleanup: - repo_clear(&subrepo); + if (!opt->priv->call_depth && !ret) { + struct string_list *csub = &opt->priv->conflicted_submodules; + struct conflicted_submodule_item *util; + const char *abbrev; + + util = xmalloc(sizeof(*util)); + util->flag = sub_flag; + util->abbrev = NULL; + if (!sub_not_initialized) { + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); + util->abbrev = xstrdup(abbrev); + } + string_list_append(csub, path)->util = util; + } + + if (!sub_not_initialized) + repo_clear(&subrepo); return ret; } @@ -4412,6 +4459,99 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void format_submodule_conflict_suggestion(struct strbuf *msg) { + struct strbuf tmp = STRBUF_INIT; + struct string_list msg_list = STRING_LIST_INIT_DUP; + int i; + + string_list_split(&msg_list, msg->buf, '\n', -1); + for (i = 0; i < msg_list.nr; i++) { + if (!i) + /* + * TRANSLATORS: This is line item of submodule conflict message + * from print_submodule_conflict_suggestion() below. For RTL + * languages, the following swap is suggested: + * " - %s\n" -> "%s - \n" + */ + strbuf_addf(&tmp, _(" - %s\n"), msg_list.items[i].string); + else + /* + * TRANSLATORS: This is line item of submodule conflict message + * from print_submodule_conflict_suggestion() below. For RTL + * languages, the following swap is suggested: + * " %s\n" -> "%s \n" + */ + strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); + } + strbuf_reset(msg); + strbuf_add(msg, tmp.buf, tmp.len); +} + +static void print_submodule_conflict_suggestion(struct string_list *csub) { + struct string_list_item *item; + struct strbuf msg = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + struct strbuf subs = STRBUF_INIT; + + if (!csub->nr) + return; + + /* + * NEEDSWORK: The steps to resolve these errors deserve a more + * detailed explanation than what is currently printed below. + */ + for_each_string_list_item(item, csub) { + struct conflicted_submodule_item *util = item->util; + + if (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED || + util->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE) + return; + } + + printf(_("Recursive 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:")); + putchar('\n'); + + for_each_string_list_item(item, csub) { + struct conflicted_submodule_item *util = item->util; + /* + * TRANSLATORS: This is a line of advice to resolve a merge conflict + * in a submodule. The second argument is the abbreviated id of the + * commit that needs to be merged. + * E.g. - go to submodule (sub), and either merge commit abc1234" + */ + strbuf_addf(&tmp, _("go to submodule (%s), and either merge commit %s\n" + "or update to an existing commit which has merged those changes"), + item->string, util->abbrev); + format_submodule_conflict_suggestion(&tmp); + strbuf_add(&msg, tmp.buf, tmp.len); + strbuf_reset(&tmp); + } + strbuf_add_separated_string_list(&subs, " ", csub); + strbuf_addstr(&tmp, _("come back to superproject and run:")); + strbuf_addf(&tmp, "\n\ngit add %s\n\n", subs.buf); + strbuf_addstr(&tmp, _("to record the above merge or update")); + format_submodule_conflict_suggestion(&tmp); + strbuf_add(&msg, tmp.buf, tmp.len); + strbuf_reset(&tmp); + + strbuf_addstr(&tmp, _("resolve any other conflicts in the superproject")); + format_submodule_conflict_suggestion(&tmp); + strbuf_add(&msg, tmp.buf, tmp.len); + strbuf_reset(&tmp); + + strbuf_addstr(&tmp, _("commit the resulting index in the superproject")); + format_submodule_conflict_suggestion(&tmp); + strbuf_add(&msg, tmp.buf, tmp.len); + + printf("%s", msg.buf); + strbuf_release(&subs); + strbuf_release(&tmp); + strbuf_release(&msg); +} + void merge_display_update_messages(struct merge_options *opt, int detailed, struct merge_result *result) @@ -4461,6 +4601,8 @@ void merge_display_update_messages(struct merge_options *opt, } string_list_clear(&olist, 0); + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); @@ -4657,6 +4799,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_enter("merge", "allocate/init", opt->repo); if (opt->priv) { clear_or_reinit_internal_opts(opt->priv, 1); + string_list_init_nodup(&opt->priv->conflicted_submodules); trace2_region_leave("merge", "allocate/init", opt->repo); return; } diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c253bf759a..414597a420 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' +test_expect_success 'merging should conflict for non fast-forward' ' + test_when_finished "git -C merge-search reset --hard" && + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + else + test_must_fail git merge c 2> actual + fi) +' + +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' @@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward ) ' +# Setup: +# - Submodule has 2 commits: a and b +# - Superproject branch 'a' adds and commits submodule pointing to 'commit a' +# - Superproject branch 'b' adds and commits submodule pointing to 'commit b' +# If these two branches are now merged, there is no merge base +test_expect_success 'setup for null merge base' ' + mkdir no-merge-base && + (cd no-merge-base && + git init && + mkdir sub && + (cd sub && + git init && + echo "file-a" > file-a && + git add file-a && + git commit -m "commit a") && + git commit --allow-empty -m init && + git branch init && + git checkout -b a init && + git add sub && + git commit -m "a" && + git switch main && + (cd sub && + echo "file-b" > file-b && + git add file-b && + git commit -m "commit b")) +' + +test_expect_success 'merging should fail with no merge base' ' + (cd no-merge-base && + git checkout -b b init && + git add sub && + git commit -m "b" && + test_must_fail git merge a >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && + grep "$sub_expect" actual + fi) +' + test_done diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..ebeca12a71 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output + fi ' test_done base-commit: 9dd64cb4d310986dd7b8ca7fff92f9b61e0bd21a -- 2.37.1.559.g78731f0fdb-goog ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v8] submodule merge: update conflict error message 2022-08-04 19:51 ` [PATCH v8] " Calvin Wan @ 2022-08-16 15:58 ` Junio C Hamano 2022-08-16 18:58 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-08-16 15:58 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Calvin Wan <calvinwan@google.com> writes: > @@ -4412,6 +4459,99 @@ static int record_conflicted_index_entries(struct merge_options *opt) > return errs; > } > > +static void format_submodule_conflict_suggestion(struct strbuf *msg) { > + struct strbuf tmp = STRBUF_INIT; > + struct string_list msg_list = STRING_LIST_INIT_DUP; > + int i; > + > + string_list_split(&msg_list, msg->buf, '\n', -1); > + for (i = 0; i < msg_list.nr; i++) { > + if (!i) > + /* > + * TRANSLATORS: This is line item of submodule conflict message > + * from print_submodule_conflict_suggestion() below. For RTL > + * languages, the following swap is suggested: > + * " - %s\n" -> "%s - \n" > + */ > + strbuf_addf(&tmp, _(" - %s\n"), msg_list.items[i].string); > + else > + /* > + * TRANSLATORS: This is line item of submodule conflict message > + * from print_submodule_conflict_suggestion() below. For RTL > + * languages, the following swap is suggested: > + * " %s\n" -> "%s \n" > + */ > + strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); > + } > + strbuf_reset(msg); > + strbuf_add(msg, tmp.buf, tmp.len); > +} Here, tmp is not released, and mst_list holds the words split out of msg. merge-ort.c | 2 ++ 1 file changed, 2 insertions(+) diff --git i/merge-ort.c w/merge-ort.c index 4cb92bdff8..cdb53770be 100644 --- i/merge-ort.c +++ w/merge-ort.c @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) { } strbuf_reset(msg); strbuf_add(msg, tmp.buf, tmp.len); + string_list_clear(&msg_list, 0); + strbuf_release(&tmp); } static void print_submodule_conflict_suggestion(struct string_list *csub) { ^ permalink raw reply related [flat|nested] 60+ messages in thread
* Re: [PATCH v8] submodule merge: update conflict error message 2022-08-16 15:58 ` Junio C Hamano @ 2022-08-16 18:58 ` Junio C Hamano 2022-08-16 19:34 ` Calvin Wan 0 siblings, 1 reply; 60+ messages in thread From: Junio C Hamano @ 2022-08-16 18:58 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Junio C Hamano <gitster@pobox.com> writes: >> + * languages, the following swap is suggested: >> + * " %s\n" -> "%s \n" >> + */ >> + strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); >> + } >> + strbuf_reset(msg); >> + strbuf_add(msg, tmp.buf, tmp.len); >> +} > > Here, tmp is not released, and mst_list holds the words split out of msg. FWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI job. > merge-ort.c | 2 ++ > 1 file changed, 2 insertions(+) > > diff --git i/merge-ort.c w/merge-ort.c > index 4cb92bdff8..cdb53770be 100644 > --- i/merge-ort.c > +++ w/merge-ort.c > @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) { > } > strbuf_reset(msg); > strbuf_add(msg, tmp.buf, tmp.len); > + string_list_clear(&msg_list, 0); > + strbuf_release(&tmp); > } > > static void print_submodule_conflict_suggestion(struct string_list *csub) { ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v8] submodule merge: update conflict error message 2022-08-16 18:58 ` Junio C Hamano @ 2022-08-16 19:34 ` Calvin Wan 2022-08-16 19:39 ` Junio C Hamano 0 siblings, 1 reply; 60+ messages in thread From: Calvin Wan @ 2022-08-16 19:34 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, newren, Johannes.Schindelin, avarab Should I re-roll with just that change? On Tue, Aug 16, 2022 at 11:58 AM Junio C Hamano <gitster@pobox.com> wrote: > > Junio C Hamano <gitster@pobox.com> writes: > > >> + * languages, the following swap is suggested: > >> + * " %s\n" -> "%s \n" > >> + */ > >> + strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); > >> + } > >> + strbuf_reset(msg); > >> + strbuf_add(msg, tmp.buf, tmp.len); > >> +} > > > > Here, tmp is not released, and mst_list holds the words split out of msg. > > FWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI > job. > > > merge-ort.c | 2 ++ > > 1 file changed, 2 insertions(+) > > > > diff --git i/merge-ort.c w/merge-ort.c > > index 4cb92bdff8..cdb53770be 100644 > > --- i/merge-ort.c > > +++ w/merge-ort.c > > @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) { > > } > > strbuf_reset(msg); > > strbuf_add(msg, tmp.buf, tmp.len); > > + string_list_clear(&msg_list, 0); > > + strbuf_release(&tmp); > > } > > > > static void print_submodule_conflict_suggestion(struct string_list *csub) { ^ permalink raw reply [flat|nested] 60+ messages in thread
* Re: [PATCH v8] submodule merge: update conflict error message 2022-08-16 19:34 ` Calvin Wan @ 2022-08-16 19:39 ` Junio C Hamano 0 siblings, 0 replies; 60+ messages in thread From: Junio C Hamano @ 2022-08-16 19:39 UTC (permalink / raw) To: Calvin Wan; +Cc: git, newren, Johannes.Schindelin, avarab Calvin Wan <calvinwan@google.com> writes: > On Tue, Aug 16, 2022 at 11:58 AM Junio C Hamano <gitster@pobox.com> wrote: >> >> Junio C Hamano <gitster@pobox.com> writes: >> >> >> + * languages, the following swap is suggested: >> >> + * " %s\n" -> "%s \n" >> >> + */ >> >> + strbuf_addf(&tmp, _(" %s\n"), msg_list.items[i].string); >> >> + } >> >> + strbuf_reset(msg); >> >> + strbuf_add(msg, tmp.buf, tmp.len); >> >> +} >> > >> > Here, tmp is not released, and mst_list holds the words split out of msg. >> >> FWIW, with this fixed, the tip of 'seen' passes the linux-leaks CI >> job. >> >> > merge-ort.c | 2 ++ >> > 1 file changed, 2 insertions(+) >> ... > > Should I re-roll with just that change? I think the topic is already in 'next', so no, it is too late for that. I queued the attached on top but haven't merged to 'next' yet. Making sure that there is no silly mistakes (like "oh, no, that temporary variable is still alive and it shouldn't be released there just yet") by lending an extra pair of eyes is what is the most useful at this point ;-) --- >8 --- Subject: [PATCH] merge-ort: plug leaks in "submodule conflict suggestion" code The helper function uses two temporary variables that it forgets to release after it is done with them. Signed-off-by: Junio C Hamano <gitster@pobox.com> --- merge-ort.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/merge-ort.c b/merge-ort.c index a52faf6e21..f33df3ff65 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -4507,6 +4507,8 @@ static void format_submodule_conflict_suggestion(struct strbuf *msg) { } strbuf_reset(msg); strbuf_add(msg, tmp.buf, tmp.len); + string_list_clear(&msg_list, 0); + strbuf_release(&tmp); } static void print_submodule_conflict_suggestion(struct string_list *csub) { -- 2.37.2-492-g20f88697f3 ^ permalink raw reply related [flat|nested] 60+ messages in thread
end of thread, other threads:[~2022-08-16 19:41 UTC | newest] Thread overview: 60+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2022-06-06 23:54 [PATCH] submodule merge: update conflict error message Calvin Wan 2022-06-07 0:48 ` Junio C Hamano 2022-06-08 17:19 ` Calvin Wan 2022-06-08 17:34 ` Glen Choo 2022-06-08 18:01 ` Calvin Wan 2022-06-08 19:13 ` Junio C Hamano 2022-06-10 23:11 ` [PATCH v2] " Calvin Wan 2022-06-11 4:53 ` Elijah Newren 2022-06-11 17:08 ` Philippe Blain 2022-06-12 6:56 ` Elijah Newren 2022-06-13 2:03 ` Calvin Wan 2022-06-12 23:30 ` Junio C Hamano 2022-06-13 1:54 ` Calvin Wan 2022-06-29 22:40 ` [PATCH v3] " Calvin Wan 2022-06-30 2:40 ` Elijah Newren 2022-06-30 19:48 ` Calvin Wan 2022-07-01 4:27 ` Elijah Newren 2022-06-30 20:35 ` Glen Choo 2022-06-30 20:45 ` Glen Choo 2022-06-30 21:08 ` Calvin Wan 2022-07-12 23:19 ` [PATCH v4] " Calvin Wan 2022-07-13 18:11 ` Junio C Hamano 2022-07-17 2:46 ` Elijah Newren 2022-07-15 12:57 ` Johannes Schindelin 2022-07-16 6:22 ` Junio C Hamano 2022-07-17 2:44 ` Elijah Newren 2022-07-18 17:03 ` Calvin Wan 2022-07-18 21:43 ` [PATCH v5] " Calvin Wan 2022-07-19 6:39 ` Junio C Hamano 2022-07-19 19:30 ` Calvin Wan 2022-07-19 20:16 ` Junio C Hamano 2022-07-19 7:13 ` Junio C Hamano 2022-07-19 19:07 ` Calvin Wan 2022-07-19 20:30 ` Junio C Hamano 2022-07-25 6:05 ` Ævar Arnfjörð Bjarmason 2022-07-25 12:11 ` Ævar Arnfjörð Bjarmason 2022-07-25 22:03 ` Calvin Wan 2022-07-25 12:31 ` Ævar Arnfjörð Bjarmason 2022-07-25 21:27 ` Calvin Wan 2022-07-26 21:00 ` [PATCH v6] " Calvin Wan 2022-07-27 1:13 ` Elijah Newren 2022-07-27 22:00 ` Calvin Wan 2022-07-28 0:41 ` Elijah Newren 2022-07-28 19:06 ` Calvin Wan 2022-07-27 9:20 ` Ævar Arnfjörð Bjarmason 2022-07-28 21:12 ` [PATCH v7] " Calvin Wan 2022-07-28 23:22 ` Ævar Arnfjörð Bjarmason 2022-07-29 0:24 ` Elijah Newren 2022-08-01 22:24 ` Calvin Wan 2022-08-01 12:06 ` Ævar Arnfjörð Bjarmason 2022-08-02 0:50 ` Junio C Hamano 2022-08-02 21:03 ` Calvin Wan 2022-08-02 21:11 ` Junio C Hamano 2022-08-02 21:55 ` Calvin Wan 2022-08-02 22:22 ` Junio C Hamano 2022-08-04 19:51 ` [PATCH v8] " Calvin Wan 2022-08-16 15:58 ` Junio C Hamano 2022-08-16 18:58 ` Junio C Hamano 2022-08-16 19:34 ` Calvin Wan 2022-08-16 19:39 ` Junio C Hamano
Code repositories for project(s) associated with this public inbox https://80x24.org/mirrors/git.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).