Skip to content

Commit df3c129

Browse files
committed
Merge branch 'en/submodule-merge-messages-fixes'
Further update the help messages given while merging submodules. * en/submodule-merge-messages-fixes: merge-ort: provide helpful submodule update message when possible merge-ort: avoid surprise with new sub_flag variable merge-ort: remove translator lego in new "submodule conflict suggestion" submodule merge: update conflict error message
2 parents 795ea87 + 565577e commit df3c129

File tree

3 files changed

+195
-17
lines changed

3 files changed

+195
-17
lines changed

merge-ort.c

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,24 @@ struct merge_options_internal {
387387

388388
/* call_depth: recursion level counter for merging merge bases */
389389
int call_depth;
390+
391+
/* field that holds submodule conflict information */
392+
struct string_list conflicted_submodules;
393+
};
394+
395+
struct conflicted_submodule_item {
396+
char *abbrev;
397+
int flag;
390398
};
391399

400+
static void conflicted_submodule_item_free(void *util, const char *str)
401+
{
402+
struct conflicted_submodule_item *item = util;
403+
404+
free(item->abbrev);
405+
free(item);
406+
}
407+
392408
struct version_info {
393409
struct object_id oid;
394410
unsigned short mode;
@@ -517,6 +533,7 @@ enum conflict_and_info_types {
517533
CONFLICT_SUBMODULE_NOT_INITIALIZED,
518534
CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,
519535
CONFLICT_SUBMODULE_MAY_HAVE_REWINDS,
536+
CONFLICT_SUBMODULE_NULL_MERGE_BASE,
520537

521538
/* Keep this entry _last_ in the list */
522539
NB_CONFLICT_TYPES,
@@ -570,6 +587,8 @@ static const char *type_short_descriptions[] = {
570587
"CONFLICT (submodule history not available)",
571588
[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =
572589
"CONFLICT (submodule may have rewinds)",
590+
[CONFLICT_SUBMODULE_NULL_MERGE_BASE] =
591+
"CONFLICT (submodule lacks merge base)"
573592
};
574593

575594
struct logical_conflict_info {
@@ -686,6 +705,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
686705

687706
mem_pool_discard(&opti->pool, 0);
688707

708+
string_list_clear_func(&opti->conflicted_submodules,
709+
conflicted_submodule_item_free);
710+
689711
/* Clean out callback_data as well. */
690712
FREE_AND_NULL(renames->callback_data);
691713
renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -1744,24 +1766,32 @@ static int merge_submodule(struct merge_options *opt,
17441766

17451767
int i;
17461768
int search = !opt->priv->call_depth;
1769+
int sub_not_initialized = 1;
1770+
int sub_flag = CONFLICT_SUBMODULE_FAILED_TO_MERGE;
17471771

17481772
/* store fallback answer in result in case we fail */
17491773
oidcpy(result, opt->priv->call_depth ? o : a);
17501774

17511775
/* we can not handle deletion conflicts */
1752-
if (is_null_oid(o))
1753-
return 0;
1754-
if (is_null_oid(a))
1755-
return 0;
1756-
if (is_null_oid(b))
1757-
return 0;
1776+
if (is_null_oid(a) || is_null_oid(b))
1777+
BUG("submodule deleted on one side; this should be handled outside of merge_submodule()");
17581778

1759-
if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
1779+
if ((sub_not_initialized = repo_submodule_init(&subrepo,
1780+
opt->repo, path, null_oid()))) {
17601781
path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,
17611782
path, NULL, NULL, NULL,
17621783
_("Failed to merge submodule %s (not checked out)"),
17631784
path);
1764-
return 0;
1785+
sub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED;
1786+
goto cleanup;
1787+
}
1788+
1789+
if (is_null_oid(o)) {
1790+
path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0,
1791+
path, NULL, NULL, NULL,
1792+
_("Failed to merge submodule %s (no merge base)"),
1793+
path);
1794+
goto cleanup;
17651795
}
17661796

17671797
if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||
@@ -1771,6 +1801,7 @@ static int merge_submodule(struct merge_options *opt,
17711801
path, NULL, NULL, NULL,
17721802
_("Failed to merge submodule %s (commits not present)"),
17731803
path);
1804+
sub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE;
17741805
goto cleanup;
17751806
}
17761807

@@ -1849,7 +1880,23 @@ static int merge_submodule(struct merge_options *opt,
18491880

18501881
object_array_clear(&merges);
18511882
cleanup:
1852-
repo_clear(&subrepo);
1883+
if (!opt->priv->call_depth && !ret) {
1884+
struct string_list *csub = &opt->priv->conflicted_submodules;
1885+
struct conflicted_submodule_item *util;
1886+
const char *abbrev;
1887+
1888+
util = xmalloc(sizeof(*util));
1889+
util->flag = sub_flag;
1890+
util->abbrev = NULL;
1891+
if (!sub_not_initialized) {
1892+
abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV);
1893+
util->abbrev = xstrdup(abbrev);
1894+
}
1895+
string_list_append(csub, path)->util = util;
1896+
}
1897+
1898+
if (!sub_not_initialized)
1899+
repo_clear(&subrepo);
18531900
return ret;
18541901
}
18551902

@@ -4434,6 +4481,63 @@ static int record_conflicted_index_entries(struct merge_options *opt)
44344481
return errs;
44354482
}
44364483

4484+
static void print_submodule_conflict_suggestion(struct string_list *csub) {
4485+
struct string_list_item *item;
4486+
struct strbuf msg = STRBUF_INIT;
4487+
struct strbuf tmp = STRBUF_INIT;
4488+
struct strbuf subs = STRBUF_INIT;
4489+
4490+
if (!csub->nr)
4491+
return;
4492+
4493+
strbuf_add_separated_string_list(&subs, " ", csub);
4494+
for_each_string_list_item(item, csub) {
4495+
struct conflicted_submodule_item *util = item->util;
4496+
4497+
/*
4498+
* NEEDSWORK: The steps to resolve these errors deserve a more
4499+
* detailed explanation than what is currently printed below.
4500+
*/
4501+
if (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED ||
4502+
util->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE)
4503+
continue;
4504+
4505+
/*
4506+
* TRANSLATORS: This is a line of advice to resolve a merge
4507+
* conflict in a submodule. The first argument is the submodule
4508+
* name, and the second argument is the abbreviated id of the
4509+
* commit that needs to be merged. For example:
4510+
* - go to submodule (mysubmodule), and either merge commit abc1234"
4511+
*/
4512+
strbuf_addf(&tmp, _(" - go to submodule (%s), and either merge commit %s\n"
4513+
" or update to an existing commit which has merged those changes\n"),
4514+
item->string, util->abbrev);
4515+
}
4516+
4517+
/*
4518+
* TRANSLATORS: This is a detailed message for resolving submodule
4519+
* conflicts. The first argument is string containing one step per
4520+
* submodule. The second is a space-separated list of submodule names.
4521+
*/
4522+
strbuf_addf(&msg,
4523+
_("Recursive merging with submodules currently only supports trivial cases.\n"
4524+
"Please manually handle the merging of each conflicted submodule.\n"
4525+
"This can be accomplished with the following steps:\n"
4526+
"%s"
4527+
" - come back to superproject and run:\n\n"
4528+
" git add %s\n\n"
4529+
" to record the above merge or update\n"
4530+
" - resolve any other conflicts in the superproject\n"
4531+
" - commit the resulting index in the superproject\n"),
4532+
tmp.buf, subs.buf);
4533+
4534+
printf("%s", msg.buf);
4535+
4536+
strbuf_release(&subs);
4537+
strbuf_release(&tmp);
4538+
strbuf_release(&msg);
4539+
}
4540+
44374541
void merge_display_update_messages(struct merge_options *opt,
44384542
int detailed,
44394543
struct merge_result *result)
@@ -4483,6 +4587,8 @@ void merge_display_update_messages(struct merge_options *opt,
44834587
}
44844588
string_list_clear(&olist, 0);
44854589

4590+
print_submodule_conflict_suggestion(&opti->conflicted_submodules);
4591+
44864592
/* Also include needed rename limit adjustment now */
44874593
diff_warn_rename_limit("merge.renamelimit",
44884594
opti->renames.needed_limit, 0);
@@ -4684,6 +4790,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
46844790
trace2_region_enter("merge", "allocate/init", opt->repo);
46854791
if (opt->priv) {
46864792
clear_or_reinit_internal_opts(opt->priv, 1);
4793+
string_list_init_nodup(&opt->priv->conflicted_submodules);
46874794
trace2_region_leave("merge", "allocate/init", opt->repo);
46884795
return;
46894796
}

t/t6437-submodule-merge.sh

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' '
103103
echo "file-c" > file-c &&
104104
git add file-c &&
105105
git commit -m "sub-c") &&
106-
git commit -a -m "c" &&
106+
git commit -a -m "c")
107+
'
107108

109+
test_expect_success 'merging should conflict for non fast-forward' '
110+
test_when_finished "git -C merge-search reset --hard" &&
111+
(cd merge-search &&
112+
git checkout -b test-nonforward-a b &&
113+
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
114+
then
115+
test_must_fail git merge c >actual &&
116+
sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
117+
grep "$sub_expect" actual
118+
else
119+
test_must_fail git merge c 2> actual
120+
fi)
121+
'
122+
123+
test_expect_success 'finish setup for merge-search' '
124+
(cd merge-search &&
108125
git checkout -b d a &&
109126
(cd sub &&
110127
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' '
129146
test_cmp expect actual)
130147
'
131148

132-
test_expect_success 'merging should conflict for non fast-forward' '
149+
test_expect_success 'merging should conflict for non fast-forward (resolution exists)' '
133150
(cd merge-search &&
134-
git checkout -b test-nonforward b &&
151+
git checkout -b test-nonforward-b b &&
135152
(cd sub &&
136153
git rev-parse --short sub-d > ../expect) &&
137154
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
138155
then
139-
test_must_fail git merge c >actual
156+
test_must_fail git merge c >actual &&
157+
sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
158+
grep "$sub_expect" actual
140159
else
141160
test_must_fail git merge c 2> actual
142161
fi &&
@@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' '
161180
) &&
162181
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
163182
then
164-
test_must_fail git merge c >actual
183+
test_must_fail git merge c >actual &&
184+
sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" &&
185+
grep "$sub_expect" actual
165186
else
166187
test_must_fail git merge c 2> actual
167188
fi &&
@@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' '
205226
git commit -a -m "f" &&
206227
207228
git checkout -b test-backward e &&
208-
test_must_fail git merge f)
229+
test_must_fail git merge f >actual &&
230+
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
231+
then
232+
sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" &&
233+
grep "$sub_expect" actual
234+
fi)
209235
'
210236

211237

@@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward
476502
)
477503
'
478504

505+
# Setup:
506+
# - Submodule has 2 commits: a and b
507+
# - Superproject branch 'a' adds and commits submodule pointing to 'commit a'
508+
# - Superproject branch 'b' adds and commits submodule pointing to 'commit b'
509+
# If these two branches are now merged, there is no merge base
510+
test_expect_success 'setup for null merge base' '
511+
mkdir no-merge-base &&
512+
(cd no-merge-base &&
513+
git init &&
514+
mkdir sub &&
515+
(cd sub &&
516+
git init &&
517+
echo "file-a" > file-a &&
518+
git add file-a &&
519+
git commit -m "commit a") &&
520+
git commit --allow-empty -m init &&
521+
git branch init &&
522+
git checkout -b a init &&
523+
git add sub &&
524+
git commit -m "a" &&
525+
git switch main &&
526+
(cd sub &&
527+
echo "file-b" > file-b &&
528+
git add file-b &&
529+
git commit -m "commit b"))
530+
'
531+
532+
test_expect_success 'merging should fail with no merge base' '
533+
(cd no-merge-base &&
534+
git checkout -b b init &&
535+
git add sub &&
536+
git commit -m "b" &&
537+
test_must_fail git merge a >actual &&
538+
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
539+
then
540+
sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" &&
541+
grep "$sub_expect" actual
542+
fi)
543+
'
544+
479545
test_done

t/t7402-submodule-rebase.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,20 @@ test_expect_success 'rebasing submodule that should conflict' '
104104
test_tick &&
105105
git commit -m fourth &&
106106
107-
test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&
107+
test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output &&
108108
git ls-files -s submodule >actual &&
109109
(
110110
cd submodule &&
111111
echo "160000 $(git rev-parse HEAD^) 1 submodule" &&
112112
echo "160000 $(git rev-parse HEAD^^) 2 submodule" &&
113113
echo "160000 $(git rev-parse HEAD) 3 submodule"
114114
) >expect &&
115-
test_cmp expect actual
115+
test_cmp expect actual &&
116+
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
117+
then
118+
sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" &&
119+
grep "$sub_expect" actual_output
120+
fi
116121
'
117122

118123
test_done

0 commit comments

Comments
 (0)