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
This commit is contained in:
commit
b1e3dd68ee
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -108,18 +133,50 @@ character instead of a newline character.
|
|||||||
Informational messages
|
Informational messages
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This always starts with a blank line (or NUL if `-z` is passed) to
|
This section provides informational messages, typically about
|
||||||
separate it from the previous sections, and then has free-form
|
conflicts. The format of the section varies significantly depending
|
||||||
messages about the merge, such as:
|
on whether `-z` is passed.
|
||||||
|
|
||||||
|
If `-z` is passed:
|
||||||
|
|
||||||
|
The output format is zero or more conflict informational records, each
|
||||||
|
of the form:
|
||||||
|
|
||||||
|
<list-of-paths><conflict-type>NUL<conflict-message>NUL
|
||||||
|
|
||||||
|
where <list-of-paths> is of the form
|
||||||
|
|
||||||
|
<number-of-paths>NUL<path1>NUL<path2>NUL...<pathN>NUL
|
||||||
|
|
||||||
|
and includes paths (or branch names) affected by the conflict or
|
||||||
|
informational message in <conflict-message>. Also, <conflict-type> is a
|
||||||
|
stable string explaining the type of conflict, such as
|
||||||
|
|
||||||
|
* "Auto-merging"
|
||||||
|
* "CONFLICT (rename/delete)"
|
||||||
|
* "CONFLICT (submodule lacks merge base)"
|
||||||
|
* "CONFLICT (binary)"
|
||||||
|
|
||||||
|
and <conflict-message> is a more detailed message about the conflict which often
|
||||||
|
(but not always) embeds the <stable-short-type-description> within it. These
|
||||||
|
strings may change in future Git versions. Some examples:
|
||||||
|
|
||||||
* "Auto-merging <file>"
|
* "Auto-merging <file>"
|
||||||
* "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
|
* "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
|
||||||
* "Failed to merge submodule <submodule> (<reason>)"
|
* "Failed to merge submodule <submodule> (no merge base)"
|
||||||
* "Warning: cannot merge binary files: <filename>"
|
* "Warning: cannot merge binary files: <filename>"
|
||||||
|
|
||||||
Note that these free-form messages will never have a NUL character
|
If `-z` is NOT passed:
|
||||||
in or between them, even if -z is passed. It is simply a large block
|
|
||||||
of text taking up the remainder of the output.
|
This section starts with a blank line to separate it from the previous
|
||||||
|
sections, and then only contains the <conflict-message> information
|
||||||
|
from the previous section (separated by newlines). These are
|
||||||
|
non-stable strings that should not be parsed by scripts, and are just
|
||||||
|
meant for human consumption. Also, note that while <conflict-message>
|
||||||
|
strings usually do not contain embedded newlines, they sometimes do.
|
||||||
|
(However, the free-form messages will never have an embedded NUL
|
||||||
|
character). So, the entire block of information is meant for human
|
||||||
|
readers as an agglomeration of all conflict messages.
|
||||||
|
|
||||||
EXIT STATUS
|
EXIT STATUS
|
||||||
-----------
|
-----------
|
||||||
@ -127,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