Merge branch 'en/merge-tree-check'
"git merge-tree" learned an option to see if it resolves cleanly without actually creating a result. * en/merge-tree-check: merge-tree: add a new --quiet flag merge-ort: add a new mergeability_only option
This commit is contained in:
@@ -65,6 +65,12 @@ OPTIONS
|
|||||||
default is to include these messages if there are merge
|
default is to include these messages if there are merge
|
||||||
conflicts, and to omit them otherwise.
|
conflicts, and to omit them otherwise.
|
||||||
|
|
||||||
|
--quiet::
|
||||||
|
Disable all output from the program. Useful when you are only
|
||||||
|
interested in the exit status. Allows merge-tree to exit
|
||||||
|
early when it finds a conflict, and allows it to avoid writing
|
||||||
|
most objects created by merges.
|
||||||
|
|
||||||
--allow-unrelated-histories::
|
--allow-unrelated-histories::
|
||||||
merge-tree will by default error out if the two branches specified
|
merge-tree will by default error out if the two branches specified
|
||||||
share no common history. This flag can be given to override that
|
share no common history. This flag can be given to override that
|
||||||
|
|||||||
@@ -490,6 +490,9 @@ 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->merge_options.mergeability_only)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
if (show_messages == -1)
|
if (show_messages == -1)
|
||||||
show_messages = !result.clean;
|
show_messages = !result.clean;
|
||||||
|
|
||||||
@@ -522,6 +525,8 @@ static int real_merge(struct merge_tree_options *o,
|
|||||||
}
|
}
|
||||||
if (o->use_stdin)
|
if (o->use_stdin)
|
||||||
putchar(line_termination);
|
putchar(line_termination);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
merge_finalize(&opt, &result);
|
merge_finalize(&opt, &result);
|
||||||
clear_merge_options(&opt);
|
clear_merge_options(&opt);
|
||||||
return !result.clean; /* result.clean < 0 handled above */
|
return !result.clean; /* result.clean < 0 handled above */
|
||||||
@@ -538,6 +543,7 @@ int cmd_merge_tree(int argc,
|
|||||||
int original_argc;
|
int original_argc;
|
||||||
const char *merge_base = NULL;
|
const char *merge_base = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
int quiet = 0;
|
||||||
|
|
||||||
const char * const merge_tree_usage[] = {
|
const char * const merge_tree_usage[] = {
|
||||||
N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
|
N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
|
||||||
@@ -552,6 +558,10 @@ int cmd_merge_tree(int argc,
|
|||||||
N_("do a trivial merge only"), MODE_TRIVIAL),
|
N_("do a trivial merge only"), MODE_TRIVIAL),
|
||||||
OPT_BOOL(0, "messages", &o.show_messages,
|
OPT_BOOL(0, "messages", &o.show_messages,
|
||||||
N_("also show informational/conflict messages")),
|
N_("also show informational/conflict messages")),
|
||||||
|
OPT_BOOL_F(0, "quiet",
|
||||||
|
&quiet,
|
||||||
|
N_("suppress all output; only exit status wanted"),
|
||||||
|
PARSE_OPT_NONEG),
|
||||||
OPT_SET_INT('z', NULL, &line_termination,
|
OPT_SET_INT('z', NULL, &line_termination,
|
||||||
N_("separate paths with the NUL character"), '\0'),
|
N_("separate paths with the NUL character"), '\0'),
|
||||||
OPT_BOOL_F(0, "name-only",
|
OPT_BOOL_F(0, "name-only",
|
||||||
@@ -583,6 +593,14 @@ int cmd_merge_tree(int argc,
|
|||||||
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);
|
||||||
|
|
||||||
|
if (quiet && o.show_messages == -1)
|
||||||
|
o.show_messages = 0;
|
||||||
|
o.merge_options.mergeability_only = quiet;
|
||||||
|
die_for_incompatible_opt2(quiet, "--quiet", o.show_messages, "--messages");
|
||||||
|
die_for_incompatible_opt2(quiet, "--quiet", o.name_only, "--name-only");
|
||||||
|
die_for_incompatible_opt2(quiet, "--quiet", o.use_stdin, "--stdin");
|
||||||
|
die_for_incompatible_opt2(quiet, "--quiet", !line_termination, "-z");
|
||||||
|
|
||||||
if (xopts.nr && o.mode == MODE_TRIVIAL)
|
if (xopts.nr && o.mode == MODE_TRIVIAL)
|
||||||
die(_("--trivial-merge is incompatible with all other options"));
|
die(_("--trivial-merge is incompatible with all other options"));
|
||||||
for (size_t x = 0; x < xopts.nr; x++)
|
for (size_t x = 0; x < xopts.nr; x++)
|
||||||
|
|||||||
38
merge-ort.c
38
merge-ort.c
@@ -2127,6 +2127,7 @@ static int handle_content_merge(struct merge_options *opt,
|
|||||||
const struct version_info *b,
|
const struct version_info *b,
|
||||||
const char *pathnames[3],
|
const char *pathnames[3],
|
||||||
const int extra_marker_size,
|
const int extra_marker_size,
|
||||||
|
const int record_object,
|
||||||
struct version_info *result)
|
struct version_info *result)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -2214,7 +2215,7 @@ static int handle_content_merge(struct merge_options *opt,
|
|||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret &&
|
if (!ret && record_object &&
|
||||||
write_object_file(result_buf.ptr, result_buf.size,
|
write_object_file(result_buf.ptr, result_buf.size,
|
||||||
OBJ_BLOB, &result->oid)) {
|
OBJ_BLOB, &result->oid)) {
|
||||||
path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,
|
path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,
|
||||||
@@ -2897,6 +2898,7 @@ static int process_renames(struct merge_options *opt,
|
|||||||
struct version_info merged;
|
struct version_info merged;
|
||||||
struct conflict_info *base, *side1, *side2;
|
struct conflict_info *base, *side1, *side2;
|
||||||
unsigned was_binary_blob = 0;
|
unsigned was_binary_blob = 0;
|
||||||
|
const int record_object = true;
|
||||||
|
|
||||||
pathnames[0] = oldpath;
|
pathnames[0] = oldpath;
|
||||||
pathnames[1] = newpath;
|
pathnames[1] = newpath;
|
||||||
@@ -2947,6 +2949,7 @@ static int process_renames(struct merge_options *opt,
|
|||||||
&side2->stages[2],
|
&side2->stages[2],
|
||||||
pathnames,
|
pathnames,
|
||||||
1 + 2 * opt->priv->call_depth,
|
1 + 2 * opt->priv->call_depth,
|
||||||
|
record_object,
|
||||||
&merged);
|
&merged);
|
||||||
if (clean_merge < 0)
|
if (clean_merge < 0)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -3061,6 +3064,7 @@ static int process_renames(struct merge_options *opt,
|
|||||||
|
|
||||||
struct conflict_info *base, *side1, *side2;
|
struct conflict_info *base, *side1, *side2;
|
||||||
int clean;
|
int clean;
|
||||||
|
const int record_object = true;
|
||||||
|
|
||||||
pathnames[0] = oldpath;
|
pathnames[0] = oldpath;
|
||||||
pathnames[other_source_index] = oldpath;
|
pathnames[other_source_index] = oldpath;
|
||||||
@@ -3080,6 +3084,7 @@ static int process_renames(struct merge_options *opt,
|
|||||||
&side2->stages[2],
|
&side2->stages[2],
|
||||||
pathnames,
|
pathnames,
|
||||||
1 + 2 * opt->priv->call_depth,
|
1 + 2 * opt->priv->call_depth,
|
||||||
|
record_object,
|
||||||
&merged);
|
&merged);
|
||||||
if (clean < 0)
|
if (clean < 0)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -3931,9 +3936,12 @@ static int write_completed_directory(struct merge_options *opt,
|
|||||||
* Write out the tree to the git object directory, and also
|
* Write out the tree to the git object directory, and also
|
||||||
* record the mode and oid in dir_info->result.
|
* record the mode and oid in dir_info->result.
|
||||||
*/
|
*/
|
||||||
|
int record_tree = (!opt->mergeability_only ||
|
||||||
|
opt->priv->call_depth);
|
||||||
dir_info->is_null = 0;
|
dir_info->is_null = 0;
|
||||||
dir_info->result.mode = S_IFDIR;
|
dir_info->result.mode = S_IFDIR;
|
||||||
if (write_tree(&dir_info->result.oid, &info->versions, offset,
|
if (record_tree &&
|
||||||
|
write_tree(&dir_info->result.oid, &info->versions, offset,
|
||||||
opt->repo->hash_algo->rawsz) < 0)
|
opt->repo->hash_algo->rawsz) < 0)
|
||||||
ret = -1;
|
ret = -1;
|
||||||
}
|
}
|
||||||
@@ -4231,10 +4239,13 @@ static int process_entry(struct merge_options *opt,
|
|||||||
struct version_info *o = &ci->stages[0];
|
struct version_info *o = &ci->stages[0];
|
||||||
struct version_info *a = &ci->stages[1];
|
struct version_info *a = &ci->stages[1];
|
||||||
struct version_info *b = &ci->stages[2];
|
struct version_info *b = &ci->stages[2];
|
||||||
|
int record_object = (!opt->mergeability_only ||
|
||||||
|
opt->priv->call_depth);
|
||||||
|
|
||||||
clean_merge = handle_content_merge(opt, path, o, a, b,
|
clean_merge = handle_content_merge(opt, path, o, a, b,
|
||||||
ci->pathnames,
|
ci->pathnames,
|
||||||
opt->priv->call_depth * 2,
|
opt->priv->call_depth * 2,
|
||||||
|
record_object,
|
||||||
&merged_file);
|
&merged_file);
|
||||||
if (clean_merge < 0)
|
if (clean_merge < 0)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -4395,6 +4406,8 @@ static int process_entries(struct merge_options *opt,
|
|||||||
STRING_LIST_INIT_NODUP,
|
STRING_LIST_INIT_NODUP,
|
||||||
NULL, 0 };
|
NULL, 0 };
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
const int record_tree = (!opt->mergeability_only ||
|
||||||
|
opt->priv->call_depth);
|
||||||
|
|
||||||
trace2_region_enter("merge", "process_entries setup", opt->repo);
|
trace2_region_enter("merge", "process_entries setup", opt->repo);
|
||||||
if (strmap_empty(&opt->priv->paths)) {
|
if (strmap_empty(&opt->priv->paths)) {
|
||||||
@@ -4454,6 +4467,12 @@ static int process_entries(struct merge_options *opt,
|
|||||||
ret = -1;
|
ret = -1;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
};
|
};
|
||||||
|
if (!ci->merged.clean && opt->mergeability_only &&
|
||||||
|
!opt->priv->call_depth) {
|
||||||
|
ret = 0;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trace2_region_leave("merge", "processing", opt->repo);
|
trace2_region_leave("merge", "processing", opt->repo);
|
||||||
@@ -4468,7 +4487,8 @@ static int process_entries(struct merge_options *opt,
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
BUG("dir_metadata accounting completely off; shouldn't happen");
|
BUG("dir_metadata accounting completely off; shouldn't happen");
|
||||||
}
|
}
|
||||||
if (write_tree(result_oid, &dir_metadata.versions, 0,
|
if (record_tree &&
|
||||||
|
write_tree(result_oid, &dir_metadata.versions, 0,
|
||||||
opt->repo->hash_algo->rawsz) < 0)
|
opt->repo->hash_algo->rawsz) < 0)
|
||||||
ret = -1;
|
ret = -1;
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -4715,6 +4735,8 @@ void merge_display_update_messages(struct merge_options *opt,
|
|||||||
|
|
||||||
if (opt->record_conflict_msgs_as_headers)
|
if (opt->record_conflict_msgs_as_headers)
|
||||||
BUG("Either display conflict messages or record them as headers, not both");
|
BUG("Either display conflict messages or record them as headers, not both");
|
||||||
|
if (opt->mergeability_only)
|
||||||
|
BUG("Displaying conflict messages incompatible with mergeability-only checks");
|
||||||
|
|
||||||
trace2_region_enter("merge", "display messages", opt->repo);
|
trace2_region_enter("merge", "display messages", opt->repo);
|
||||||
|
|
||||||
@@ -5171,10 +5193,12 @@ redo:
|
|||||||
result->path_messages = &opt->priv->conflicts;
|
result->path_messages = &opt->priv->conflicts;
|
||||||
|
|
||||||
if (result->clean >= 0) {
|
if (result->clean >= 0) {
|
||||||
result->tree = parse_tree_indirect(&working_tree_oid);
|
if (!opt->mergeability_only) {
|
||||||
if (!result->tree)
|
result->tree = parse_tree_indirect(&working_tree_oid);
|
||||||
die(_("unable to read tree (%s)"),
|
if (!result->tree)
|
||||||
oid_to_hex(&working_tree_oid));
|
die(_("unable to read tree (%s)"),
|
||||||
|
oid_to_hex(&working_tree_oid));
|
||||||
|
}
|
||||||
/* existence of conflicted entries implies unclean */
|
/* existence of conflicted entries implies unclean */
|
||||||
result->clean &= strmap_empty(&opt->priv->conflicted);
|
result->clean &= strmap_empty(&opt->priv->conflicted);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ struct merge_options {
|
|||||||
/* miscellaneous control options */
|
/* miscellaneous control options */
|
||||||
const char *subtree_shift;
|
const char *subtree_shift;
|
||||||
unsigned renormalize : 1;
|
unsigned renormalize : 1;
|
||||||
|
unsigned mergeability_only : 1; /* exit early, write fewer objects */
|
||||||
unsigned record_conflict_msgs_as_headers : 1;
|
unsigned record_conflict_msgs_as_headers : 1;
|
||||||
const char *msg_header_prefix;
|
const char *msg_header_prefix;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,25 @@ test_expect_success setup '
|
|||||||
git commit -m first-commit
|
git commit -m first-commit
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--quiet on clean merge' '
|
||||||
|
# Get rid of loose objects to start with
|
||||||
|
git gc &&
|
||||||
|
echo "0 objects, 0 kilobytes" >expect &&
|
||||||
|
git count-objects >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# Ensure merge is successful (exit code of 0)
|
||||||
|
git merge-tree --write-tree --quiet side1 side3 >output &&
|
||||||
|
|
||||||
|
# Ensure there is no output
|
||||||
|
test_must_be_empty output &&
|
||||||
|
|
||||||
|
# Ensure no loose objects written (all new objects written would have
|
||||||
|
# been in "outer layer" of the merge)
|
||||||
|
git count-objects >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'Clean merge' '
|
test_expect_success 'Clean merge' '
|
||||||
TREE_OID=$(git merge-tree --write-tree side1 side3) &&
|
TREE_OID=$(git merge-tree --write-tree side1 side3) &&
|
||||||
q_to_tab <<-EOF >expect &&
|
q_to_tab <<-EOF >expect &&
|
||||||
@@ -72,6 +91,25 @@ test_expect_success 'Failed merge without rename detection' '
|
|||||||
grep "CONFLICT (modify/delete): numbers deleted" out
|
grep "CONFLICT (modify/delete): numbers deleted" out
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--quiet on conflicted merge' '
|
||||||
|
# Get rid of loose objects to start with
|
||||||
|
git gc &&
|
||||||
|
echo "0 objects, 0 kilobytes" >expect &&
|
||||||
|
git count-objects >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# Ensure merge has conflict
|
||||||
|
test_expect_code 1 git merge-tree --write-tree --quiet side1 side2 >output &&
|
||||||
|
|
||||||
|
# Ensure there is no output
|
||||||
|
test_must_be_empty output &&
|
||||||
|
|
||||||
|
# Ensure no loose objects written (all new objects written would have
|
||||||
|
# been in "outer layer" of the merge)
|
||||||
|
git count-objects >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'Content merge and a few conflicts' '
|
test_expect_success 'Content merge and a few conflicts' '
|
||||||
git checkout side1^0 &&
|
git checkout side1^0 &&
|
||||||
test_must_fail git merge side2 &&
|
test_must_fail git merge side2 &&
|
||||||
|
|||||||
Reference in New Issue
Block a user