Skip to content

Commit b1e3dd6

Browse files
committed
Merge branch 'en/merge-tree-sequence'
"git merge-tree --stdin" is a new way to request a series of merges and report the merge results. * en/merge-tree-sequence: merge-tree: support multiple batched merges with --stdin merge-tree: update documentation for differences in -z output
2 parents d32dd8a + ec1edbc commit b1e3dd6

File tree

3 files changed

+148
-11
lines changed

3 files changed

+148
-11
lines changed

Documentation/git-merge-tree.txt

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ Whereas for a conflicted merge, the output is by default of the form:
8181

8282
These are discussed individually below.
8383

84+
However, there is an exception. If `--stdin` is passed, then there is
85+
an extra section at the beginning, a NUL character at the end, and then
86+
all the sections repeat for each line of input. Thus, if the first merge
87+
is conflicted and the second is clean, the output would be of the form:
88+
89+
<Merge status>
90+
<OID of toplevel tree>
91+
<Conflicted file info>
92+
<Informational messages>
93+
NUL
94+
<Merge status>
95+
<OID of toplevel tree>
96+
NUL
97+
98+
[[MS]]
99+
Merge status
100+
~~~~~~~~~~~~
101+
102+
This is an integer status followed by a NUL character. The integer status is:
103+
104+
0: merge had conflicts
105+
1: merge was clean
106+
&lt;0: something prevented the merge from running (e.g. access to repository
107+
objects denied by filesystem)
108+
84109
[[OIDTLT]]
85110
OID of toplevel tree
86111
~~~~~~~~~~~~~~~~~~~~
@@ -108,26 +133,61 @@ character instead of a newline character.
108133
Informational messages
109134
~~~~~~~~~~~~~~~~~~~~~~
110135

111-
This always starts with a blank line (or NUL if `-z` is passed) to
112-
separate it from the previous sections, and then has free-form
113-
messages about the merge, such as:
136+
This section provides informational messages, typically about
137+
conflicts. The format of the section varies significantly depending
138+
on whether `-z` is passed.
139+
140+
If `-z` is passed:
141+
142+
The output format is zero or more conflict informational records, each
143+
of the form:
144+
145+
<list-of-paths><conflict-type>NUL<conflict-message>NUL
146+
147+
where <list-of-paths> is of the form
148+
149+
<number-of-paths>NUL<path1>NUL<path2>NUL...<pathN>NUL
150+
151+
and includes paths (or branch names) affected by the conflict or
152+
informational message in <conflict-message>. Also, <conflict-type> is a
153+
stable string explaining the type of conflict, such as
154+
155+
* "Auto-merging"
156+
* "CONFLICT (rename/delete)"
157+
* "CONFLICT (submodule lacks merge base)"
158+
* "CONFLICT (binary)"
159+
160+
and <conflict-message> is a more detailed message about the conflict which often
161+
(but not always) embeds the <stable-short-type-description> within it. These
162+
strings may change in future Git versions. Some examples:
114163

115164
* "Auto-merging <file>"
116165
* "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
117-
* "Failed to merge submodule <submodule> (<reason>)"
166+
* "Failed to merge submodule <submodule> (no merge base)"
118167
* "Warning: cannot merge binary files: <filename>"
119168

120-
Note that these free-form messages will never have a NUL character
121-
in or between them, even if -z is passed. It is simply a large block
122-
of text taking up the remainder of the output.
169+
If `-z` is NOT passed:
170+
171+
This section starts with a blank line to separate it from the previous
172+
sections, and then only contains the <conflict-message> information
173+
from the previous section (separated by newlines). These are
174+
non-stable strings that should not be parsed by scripts, and are just
175+
meant for human consumption. Also, note that while <conflict-message>
176+
strings usually do not contain embedded newlines, they sometimes do.
177+
(However, the free-form messages will never have an embedded NUL
178+
character). So, the entire block of information is meant for human
179+
readers as an agglomeration of all conflict messages.
123180

124181
EXIT STATUS
125182
-----------
126183

127184
For a successful, non-conflicted merge, the exit status is 0. When the
128185
merge has conflicts, the exit status is 1. If the merge is not able to
129186
complete (or start) due to some kind of error, the exit status is
130-
something other than 0 or 1 (and the output is unspecified).
187+
something other than 0 or 1 (and the output is unspecified). When
188+
--stdin is passed, the return status is 0 for both successful and
189+
conflicted merges, and something other than 0 or 1 if it cannot complete
190+
all the requested merges.
131191

132192
USAGE NOTES
133193
-----------

builtin/merge-tree.c

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ struct merge_tree_options {
402402
int allow_unrelated_histories;
403403
int show_messages;
404404
int name_only;
405+
int use_stdin;
405406
};
406407

407408
static int real_merge(struct merge_tree_options *o,
@@ -412,6 +413,7 @@ static int real_merge(struct merge_tree_options *o,
412413
struct commit_list *merge_bases = NULL;
413414
struct merge_options opt;
414415
struct merge_result result = { 0 };
416+
int show_messages = o->show_messages;
415417

416418
parent1 = get_merge_parent(branch1);
417419
if (!parent1)
@@ -443,9 +445,11 @@ static int real_merge(struct merge_tree_options *o,
443445
if (result.clean < 0)
444446
die(_("failure to merge"));
445447

446-
if (o->show_messages == -1)
447-
o->show_messages = !result.clean;
448+
if (show_messages == -1)
449+
show_messages = !result.clean;
448450

451+
if (o->use_stdin)
452+
printf("%d%c", result.clean, line_termination);
449453
printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination);
450454
if (!result.clean) {
451455
struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
@@ -467,11 +471,13 @@ static int real_merge(struct merge_tree_options *o,
467471
}
468472
string_list_clear(&conflicted_files, 1);
469473
}
470-
if (o->show_messages) {
474+
if (show_messages) {
471475
putchar(line_termination);
472476
merge_display_update_messages(&opt, line_termination == '\0',
473477
&result);
474478
}
479+
if (o->use_stdin)
480+
putchar(line_termination);
475481
merge_finalize(&opt, &result);
476482
return !result.clean; /* result.clean < 0 handled above */
477483
}
@@ -505,13 +511,43 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
505511
&o.allow_unrelated_histories,
506512
N_("allow merging unrelated histories"),
507513
PARSE_OPT_NONEG),
514+
OPT_BOOL_F(0, "stdin",
515+
&o.use_stdin,
516+
N_("perform multiple merges, one per line of input"),
517+
PARSE_OPT_NONEG),
508518
OPT_END()
509519
};
510520

511521
/* Parse arguments */
512522
original_argc = argc - 1; /* ignoring argv[0] */
513523
argc = parse_options(argc, argv, prefix, mt_options,
514524
merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
525+
526+
/* Handle --stdin */
527+
if (o.use_stdin) {
528+
struct strbuf buf = STRBUF_INIT;
529+
530+
if (o.mode == MODE_TRIVIAL)
531+
die(_("--trivial-merge is incompatible with all other options"));
532+
line_termination = '\0';
533+
while (strbuf_getline_lf(&buf, stdin) != EOF) {
534+
struct strbuf **split;
535+
int result;
536+
537+
split = strbuf_split(&buf, ' ');
538+
if (!split[0] || !split[1] || split[2])
539+
die(_("malformed input line: '%s'."), buf.buf);
540+
strbuf_rtrim(split[0]);
541+
result = real_merge(&o, split[0]->buf, split[1]->buf, prefix);
542+
if (result < 0)
543+
die(_("merging cannot continue; got unclean result of %d"), result);
544+
strbuf_list_free(split);
545+
}
546+
strbuf_release(&buf);
547+
return 0;
548+
}
549+
550+
/* Figure out which mode to use */
515551
switch (o.mode) {
516552
default:
517553
BUG("unexpected command mode %d", o.mode);

t/t4301-merge-tree-write-tree.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,4 +819,45 @@ test_expect_success SANITY 'merge-ort fails gracefully in a read-only repository
819819
test_must_fail git -C read-only merge-tree side1 side2
820820
'
821821

822+
test_expect_success '--stdin with both a successful and a conflicted merge' '
823+
printf "side1 side3\nside1 side2" | git merge-tree --stdin >actual &&
824+
825+
git checkout side1^0 &&
826+
git merge side3 &&
827+
828+
printf "1\0" >expect &&
829+
git rev-parse HEAD^{tree} | lf_to_nul >>expect &&
830+
printf "\0" >>expect &&
831+
832+
git checkout side1^0 &&
833+
test_must_fail git merge side2 &&
834+
sed s/HEAD/side1/ greeting >tmp &&
835+
mv tmp greeting &&
836+
git add -u &&
837+
git mv whatever~HEAD whatever~side1 &&
838+
839+
printf "0\0" >>expect &&
840+
git write-tree | lf_to_nul >>expect &&
841+
842+
cat <<-EOF | q_to_tab | lf_to_nul >>expect &&
843+
100644 $(git rev-parse side1~1:greeting) 1Qgreeting
844+
100644 $(git rev-parse side1:greeting) 2Qgreeting
845+
100644 $(git rev-parse side2:greeting) 3Qgreeting
846+
100644 $(git rev-parse side1~1:whatever) 1Qwhatever~side1
847+
100644 $(git rev-parse side1:whatever) 2Qwhatever~side1
848+
EOF
849+
850+
q_to_nul <<-EOF >>expect &&
851+
Q1QgreetingQAuto-mergingQAuto-merging greeting
852+
Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
853+
Q1QnumbersQAuto-mergingQAuto-merging numbers
854+
Q2Qwhatever~side1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
855+
Q1Qwhatever~side1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree.
856+
EOF
857+
858+
printf "\0\0" >>expect &&
859+
860+
test_cmp expect actual
861+
'
862+
822863
test_done

0 commit comments

Comments
 (0)