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@xxxxxxxxxx> --- 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