Skip to content

Commit ec1edbc

Browse files
newrengitster
authored andcommitted
merge-tree: support multiple batched merges with --stdin
Add an option, --stdin, to merge-tree which will accept lines of input with two branches to merge per line, and which will perform all the merges and give output for each in turn. This option implies -z, and modifies the output to also include a merge status since the exit code of the program can no longer convey that information now that multiple merges are involved. This could be useful, for example, by Git hosting providers. When one branch is updated, one may want to check whether all code reviews targetting that branch can still cleanly merge. Avoiding the overhead of starting up a separate process for each of those code reviews might provide significant savings in a repository with many code reviews. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a9f5bb8 commit ec1edbc

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

Documentation/git-merge-tree.txt

Lines changed: 29 additions & 1 deletion
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
~~~~~~~~~~~~~~~~~~~~
@@ -159,7 +184,10 @@ EXIT STATUS
159184
For a successful, non-conflicted merge, the exit status is 0. When the
160185
merge has conflicts, the exit status is 1. If the merge is not able to
161186
complete (or start) due to some kind of error, the exit status is
162-
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.
163191

164192
USAGE NOTES
165193
-----------

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)