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 <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
a9f5bb83e0
commit
ec1edbcb56
@ -81,6 +81,31 @@ Whereas for a conflicted merge, the output is by default of the form:
|
|||||||
|
|
||||||
These are discussed individually below.
|
These are discussed individually below.
|
||||||
|
|
||||||
|
However, there is an exception. If `--stdin` is passed, then there is
|
||||||
|
an extra section at the beginning, a NUL character at the end, and then
|
||||||
|
all the sections repeat for each line of input. Thus, if the first merge
|
||||||
|
is conflicted and the second is clean, the output would be of the form:
|
||||||
|
|
||||||
|
<Merge status>
|
||||||
|
<OID of toplevel tree>
|
||||||
|
<Conflicted file info>
|
||||||
|
<Informational messages>
|
||||||
|
NUL
|
||||||
|
<Merge status>
|
||||||
|
<OID of toplevel tree>
|
||||||
|
NUL
|
||||||
|
|
||||||
|
[[MS]]
|
||||||
|
Merge status
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is an integer status followed by a NUL character. The integer status is:
|
||||||
|
|
||||||
|
0: merge had conflicts
|
||||||
|
1: merge was clean
|
||||||
|
<0: something prevented the merge from running (e.g. access to repository
|
||||||
|
objects denied by filesystem)
|
||||||
|
|
||||||
[[OIDTLT]]
|
[[OIDTLT]]
|
||||||
OID of toplevel tree
|
OID of toplevel tree
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -159,7 +184,10 @@ EXIT STATUS
|
|||||||
For a successful, non-conflicted merge, the exit status is 0. When the
|
For a successful, non-conflicted merge, the exit status is 0. When the
|
||||||
merge has conflicts, the exit status is 1. If the merge is not able to
|
merge has conflicts, the exit status is 1. If the merge is not able to
|
||||||
complete (or start) due to some kind of error, the exit status is
|
complete (or start) due to some kind of error, the exit status is
|
||||||
something other than 0 or 1 (and the output is unspecified).
|
something other than 0 or 1 (and the output is unspecified). When
|
||||||
|
--stdin is passed, the return status is 0 for both successful and
|
||||||
|
conflicted merges, and something other than 0 or 1 if it cannot complete
|
||||||
|
all the requested merges.
|
||||||
|
|
||||||
USAGE NOTES
|
USAGE NOTES
|
||||||
-----------
|
-----------
|
||||||
|
@ -402,6 +402,7 @@ struct merge_tree_options {
|
|||||||
int allow_unrelated_histories;
|
int allow_unrelated_histories;
|
||||||
int show_messages;
|
int show_messages;
|
||||||
int name_only;
|
int name_only;
|
||||||
|
int use_stdin;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int real_merge(struct merge_tree_options *o,
|
static int real_merge(struct merge_tree_options *o,
|
||||||
@ -412,6 +413,7 @@ static int real_merge(struct merge_tree_options *o,
|
|||||||
struct commit_list *merge_bases = NULL;
|
struct commit_list *merge_bases = NULL;
|
||||||
struct merge_options opt;
|
struct merge_options opt;
|
||||||
struct merge_result result = { 0 };
|
struct merge_result result = { 0 };
|
||||||
|
int show_messages = o->show_messages;
|
||||||
|
|
||||||
parent1 = get_merge_parent(branch1);
|
parent1 = get_merge_parent(branch1);
|
||||||
if (!parent1)
|
if (!parent1)
|
||||||
@ -443,9 +445,11 @@ static int real_merge(struct merge_tree_options *o,
|
|||||||
if (result.clean < 0)
|
if (result.clean < 0)
|
||||||
die(_("failure to merge"));
|
die(_("failure to merge"));
|
||||||
|
|
||||||
if (o->show_messages == -1)
|
if (show_messages == -1)
|
||||||
o->show_messages = !result.clean;
|
show_messages = !result.clean;
|
||||||
|
|
||||||
|
if (o->use_stdin)
|
||||||
|
printf("%d%c", result.clean, line_termination);
|
||||||
printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination);
|
printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination);
|
||||||
if (!result.clean) {
|
if (!result.clean) {
|
||||||
struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
|
struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
|
||||||
@ -467,11 +471,13 @@ static int real_merge(struct merge_tree_options *o,
|
|||||||
}
|
}
|
||||||
string_list_clear(&conflicted_files, 1);
|
string_list_clear(&conflicted_files, 1);
|
||||||
}
|
}
|
||||||
if (o->show_messages) {
|
if (show_messages) {
|
||||||
putchar(line_termination);
|
putchar(line_termination);
|
||||||
merge_display_update_messages(&opt, line_termination == '\0',
|
merge_display_update_messages(&opt, line_termination == '\0',
|
||||||
&result);
|
&result);
|
||||||
}
|
}
|
||||||
|
if (o->use_stdin)
|
||||||
|
putchar(line_termination);
|
||||||
merge_finalize(&opt, &result);
|
merge_finalize(&opt, &result);
|
||||||
return !result.clean; /* result.clean < 0 handled above */
|
return !result.clean; /* result.clean < 0 handled above */
|
||||||
}
|
}
|
||||||
@ -505,6 +511,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
|||||||
&o.allow_unrelated_histories,
|
&o.allow_unrelated_histories,
|
||||||
N_("allow merging unrelated histories"),
|
N_("allow merging unrelated histories"),
|
||||||
PARSE_OPT_NONEG),
|
PARSE_OPT_NONEG),
|
||||||
|
OPT_BOOL_F(0, "stdin",
|
||||||
|
&o.use_stdin,
|
||||||
|
N_("perform multiple merges, one per line of input"),
|
||||||
|
PARSE_OPT_NONEG),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -512,6 +522,32 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
|
|||||||
original_argc = argc - 1; /* ignoring argv[0] */
|
original_argc = argc - 1; /* ignoring argv[0] */
|
||||||
argc = parse_options(argc, argv, prefix, mt_options,
|
argc = parse_options(argc, argv, prefix, mt_options,
|
||||||
merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
|
merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
|
||||||
|
|
||||||
|
/* Handle --stdin */
|
||||||
|
if (o.use_stdin) {
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (o.mode == MODE_TRIVIAL)
|
||||||
|
die(_("--trivial-merge is incompatible with all other options"));
|
||||||
|
line_termination = '\0';
|
||||||
|
while (strbuf_getline_lf(&buf, stdin) != EOF) {
|
||||||
|
struct strbuf **split;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
split = strbuf_split(&buf, ' ');
|
||||||
|
if (!split[0] || !split[1] || split[2])
|
||||||
|
die(_("malformed input line: '%s'."), buf.buf);
|
||||||
|
strbuf_rtrim(split[0]);
|
||||||
|
result = real_merge(&o, split[0]->buf, split[1]->buf, prefix);
|
||||||
|
if (result < 0)
|
||||||
|
die(_("merging cannot continue; got unclean result of %d"), result);
|
||||||
|
strbuf_list_free(split);
|
||||||
|
}
|
||||||
|
strbuf_release(&buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Figure out which mode to use */
|
||||||
switch (o.mode) {
|
switch (o.mode) {
|
||||||
default:
|
default:
|
||||||
BUG("unexpected command mode %d", o.mode);
|
BUG("unexpected command mode %d", o.mode);
|
||||||
|
@ -819,4 +819,45 @@ test_expect_success SANITY 'merge-ort fails gracefully in a read-only repository
|
|||||||
test_must_fail git -C read-only merge-tree side1 side2
|
test_must_fail git -C read-only merge-tree side1 side2
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--stdin with both a successful and a conflicted merge' '
|
||||||
|
printf "side1 side3\nside1 side2" | git merge-tree --stdin >actual &&
|
||||||
|
|
||||||
|
git checkout side1^0 &&
|
||||||
|
git merge side3 &&
|
||||||
|
|
||||||
|
printf "1\0" >expect &&
|
||||||
|
git rev-parse HEAD^{tree} | lf_to_nul >>expect &&
|
||||||
|
printf "\0" >>expect &&
|
||||||
|
|
||||||
|
git checkout side1^0 &&
|
||||||
|
test_must_fail git merge side2 &&
|
||||||
|
sed s/HEAD/side1/ greeting >tmp &&
|
||||||
|
mv tmp greeting &&
|
||||||
|
git add -u &&
|
||||||
|
git mv whatever~HEAD whatever~side1 &&
|
||||||
|
|
||||||
|
printf "0\0" >>expect &&
|
||||||
|
git write-tree | lf_to_nul >>expect &&
|
||||||
|
|
||||||
|
cat <<-EOF | q_to_tab | lf_to_nul >>expect &&
|
||||||
|
100644 $(git rev-parse side1~1:greeting) 1Qgreeting
|
||||||
|
100644 $(git rev-parse side1:greeting) 2Qgreeting
|
||||||
|
100644 $(git rev-parse side2:greeting) 3Qgreeting
|
||||||
|
100644 $(git rev-parse side1~1:whatever) 1Qwhatever~side1
|
||||||
|
100644 $(git rev-parse side1:whatever) 2Qwhatever~side1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
q_to_nul <<-EOF >>expect &&
|
||||||
|
Q1QgreetingQAuto-mergingQAuto-merging greeting
|
||||||
|
Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
|
||||||
|
Q1QnumbersQAuto-mergingQAuto-merging numbers
|
||||||
|
Q2Qwhatever~side1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
|
||||||
|
Q1Qwhatever~side1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
printf "\0\0" >>expect &&
|
||||||
|
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user