Skip to content

Commit f545f40

Browse files
committed
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
2 parents 17d9dbd + 29d7bf1 commit f545f40

File tree

5 files changed

+94
-7
lines changed

5 files changed

+94
-7
lines changed

Documentation/git-merge-tree.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ OPTIONS
6565
default is to include these messages if there are merge
6666
conflicts, and to omit them otherwise.
6767
68+
--quiet::
69+
Disable all output from the program. Useful when you are only
70+
interested in the exit status. Allows merge-tree to exit
71+
early when it finds a conflict, and allows it to avoid writing
72+
most objects created by merges.
73+
6874
--allow-unrelated-histories::
6975
merge-tree will by default error out if the two branches specified
7076
share no common history. This flag can be given to override that

builtin/merge-tree.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ static int real_merge(struct merge_tree_options *o,
490490
if (result.clean < 0)
491491
die(_("failure to merge"));
492492

493+
if (o->merge_options.mergeability_only)
494+
goto cleanup;
495+
493496
if (show_messages == -1)
494497
show_messages = !result.clean;
495498

@@ -522,6 +525,8 @@ static int real_merge(struct merge_tree_options *o,
522525
}
523526
if (o->use_stdin)
524527
putchar(line_termination);
528+
529+
cleanup:
525530
merge_finalize(&opt, &result);
526531
clear_merge_options(&opt);
527532
return !result.clean; /* result.clean < 0 handled above */
@@ -538,6 +543,7 @@ int cmd_merge_tree(int argc,
538543
int original_argc;
539544
const char *merge_base = NULL;
540545
int ret;
546+
int quiet = 0;
541547

542548
const char * const merge_tree_usage[] = {
543549
N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
@@ -552,6 +558,10 @@ int cmd_merge_tree(int argc,
552558
N_("do a trivial merge only"), MODE_TRIVIAL),
553559
OPT_BOOL(0, "messages", &o.show_messages,
554560
N_("also show informational/conflict messages")),
561+
OPT_BOOL_F(0, "quiet",
562+
&quiet,
563+
N_("suppress all output; only exit status wanted"),
564+
PARSE_OPT_NONEG),
555565
OPT_SET_INT('z', NULL, &line_termination,
556566
N_("separate paths with the NUL character"), '\0'),
557567
OPT_BOOL_F(0, "name-only",
@@ -583,6 +593,14 @@ int cmd_merge_tree(int argc,
583593
argc = parse_options(argc, argv, prefix, mt_options,
584594
merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
585595

596+
if (quiet && o.show_messages == -1)
597+
o.show_messages = 0;
598+
o.merge_options.mergeability_only = quiet;
599+
die_for_incompatible_opt2(quiet, "--quiet", o.show_messages, "--messages");
600+
die_for_incompatible_opt2(quiet, "--quiet", o.name_only, "--name-only");
601+
die_for_incompatible_opt2(quiet, "--quiet", o.use_stdin, "--stdin");
602+
die_for_incompatible_opt2(quiet, "--quiet", !line_termination, "-z");
603+
586604
if (xopts.nr && o.mode == MODE_TRIVIAL)
587605
die(_("--trivial-merge is incompatible with all other options"));
588606
for (size_t x = 0; x < xopts.nr; x++)

merge-ort.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,7 @@ static int handle_content_merge(struct merge_options *opt,
21272127
const struct version_info *b,
21282128
const char *pathnames[3],
21292129
const int extra_marker_size,
2130+
const int record_object,
21302131
struct version_info *result)
21312132
{
21322133
/*
@@ -2214,7 +2215,7 @@ static int handle_content_merge(struct merge_options *opt,
22142215
ret = -1;
22152216
}
22162217

2217-
if (!ret &&
2218+
if (!ret && record_object &&
22182219
write_object_file(result_buf.ptr, result_buf.size,
22192220
OBJ_BLOB, &result->oid)) {
22202221
path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,
@@ -2897,6 +2898,7 @@ static int process_renames(struct merge_options *opt,
28972898
struct version_info merged;
28982899
struct conflict_info *base, *side1, *side2;
28992900
unsigned was_binary_blob = 0;
2901+
const int record_object = true;
29002902

29012903
pathnames[0] = oldpath;
29022904
pathnames[1] = newpath;
@@ -2947,6 +2949,7 @@ static int process_renames(struct merge_options *opt,
29472949
&side2->stages[2],
29482950
pathnames,
29492951
1 + 2 * opt->priv->call_depth,
2952+
record_object,
29502953
&merged);
29512954
if (clean_merge < 0)
29522955
return -1;
@@ -3061,6 +3064,7 @@ static int process_renames(struct merge_options *opt,
30613064

30623065
struct conflict_info *base, *side1, *side2;
30633066
int clean;
3067+
const int record_object = true;
30643068

30653069
pathnames[0] = oldpath;
30663070
pathnames[other_source_index] = oldpath;
@@ -3080,6 +3084,7 @@ static int process_renames(struct merge_options *opt,
30803084
&side2->stages[2],
30813085
pathnames,
30823086
1 + 2 * opt->priv->call_depth,
3087+
record_object,
30833088
&merged);
30843089
if (clean < 0)
30853090
return -1;
@@ -3931,9 +3936,12 @@ static int write_completed_directory(struct merge_options *opt,
39313936
* Write out the tree to the git object directory, and also
39323937
* record the mode and oid in dir_info->result.
39333938
*/
3939+
int record_tree = (!opt->mergeability_only ||
3940+
opt->priv->call_depth);
39343941
dir_info->is_null = 0;
39353942
dir_info->result.mode = S_IFDIR;
3936-
if (write_tree(&dir_info->result.oid, &info->versions, offset,
3943+
if (record_tree &&
3944+
write_tree(&dir_info->result.oid, &info->versions, offset,
39373945
opt->repo->hash_algo->rawsz) < 0)
39383946
ret = -1;
39393947
}
@@ -4231,10 +4239,13 @@ static int process_entry(struct merge_options *opt,
42314239
struct version_info *o = &ci->stages[0];
42324240
struct version_info *a = &ci->stages[1];
42334241
struct version_info *b = &ci->stages[2];
4242+
int record_object = (!opt->mergeability_only ||
4243+
opt->priv->call_depth);
42344244

42354245
clean_merge = handle_content_merge(opt, path, o, a, b,
42364246
ci->pathnames,
42374247
opt->priv->call_depth * 2,
4248+
record_object,
42384249
&merged_file);
42394250
if (clean_merge < 0)
42404251
return -1;
@@ -4395,6 +4406,8 @@ static int process_entries(struct merge_options *opt,
43954406
STRING_LIST_INIT_NODUP,
43964407
NULL, 0 };
43974408
int ret = 0;
4409+
const int record_tree = (!opt->mergeability_only ||
4410+
opt->priv->call_depth);
43984411

43994412
trace2_region_enter("merge", "process_entries setup", opt->repo);
44004413
if (strmap_empty(&opt->priv->paths)) {
@@ -4454,6 +4467,12 @@ static int process_entries(struct merge_options *opt,
44544467
ret = -1;
44554468
goto cleanup;
44564469
};
4470+
if (!ci->merged.clean && opt->mergeability_only &&
4471+
!opt->priv->call_depth) {
4472+
ret = 0;
4473+
goto cleanup;
4474+
}
4475+
44574476
}
44584477
}
44594478
trace2_region_leave("merge", "processing", opt->repo);
@@ -4468,7 +4487,8 @@ static int process_entries(struct merge_options *opt,
44684487
fflush(stdout);
44694488
BUG("dir_metadata accounting completely off; shouldn't happen");
44704489
}
4471-
if (write_tree(result_oid, &dir_metadata.versions, 0,
4490+
if (record_tree &&
4491+
write_tree(result_oid, &dir_metadata.versions, 0,
44724492
opt->repo->hash_algo->rawsz) < 0)
44734493
ret = -1;
44744494
cleanup:
@@ -4715,6 +4735,8 @@ void merge_display_update_messages(struct merge_options *opt,
47154735

47164736
if (opt->record_conflict_msgs_as_headers)
47174737
BUG("Either display conflict messages or record them as headers, not both");
4738+
if (opt->mergeability_only)
4739+
BUG("Displaying conflict messages incompatible with mergeability-only checks");
47184740

47194741
trace2_region_enter("merge", "display messages", opt->repo);
47204742

@@ -5171,10 +5193,12 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
51715193
result->path_messages = &opt->priv->conflicts;
51725194

51735195
if (result->clean >= 0) {
5174-
result->tree = parse_tree_indirect(&working_tree_oid);
5175-
if (!result->tree)
5176-
die(_("unable to read tree (%s)"),
5177-
oid_to_hex(&working_tree_oid));
5196+
if (!opt->mergeability_only) {
5197+
result->tree = parse_tree_indirect(&working_tree_oid);
5198+
if (!result->tree)
5199+
die(_("unable to read tree (%s)"),
5200+
oid_to_hex(&working_tree_oid));
5201+
}
51785202
/* existence of conflicted entries implies unclean */
51795203
result->clean &= strmap_empty(&opt->priv->conflicted);
51805204
}

merge-ort.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ struct merge_options {
8383
/* miscellaneous control options */
8484
const char *subtree_shift;
8585
unsigned renormalize : 1;
86+
unsigned mergeability_only : 1; /* exit early, write fewer objects */
8687
unsigned record_conflict_msgs_as_headers : 1;
8788
const char *msg_header_prefix;
8889

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ test_expect_success setup '
5454
git commit -m first-commit
5555
'
5656

57+
test_expect_success '--quiet on clean merge' '
58+
# Get rid of loose objects to start with
59+
git gc &&
60+
echo "0 objects, 0 kilobytes" >expect &&
61+
git count-objects >actual &&
62+
test_cmp expect actual &&
63+
64+
# Ensure merge is successful (exit code of 0)
65+
git merge-tree --write-tree --quiet side1 side3 >output &&
66+
67+
# Ensure there is no output
68+
test_must_be_empty output &&
69+
70+
# Ensure no loose objects written (all new objects written would have
71+
# been in "outer layer" of the merge)
72+
git count-objects >actual &&
73+
test_cmp expect actual
74+
'
75+
5776
test_expect_success 'Clean merge' '
5877
TREE_OID=$(git merge-tree --write-tree side1 side3) &&
5978
q_to_tab <<-EOF >expect &&
@@ -72,6 +91,25 @@ test_expect_success 'Failed merge without rename detection' '
7291
grep "CONFLICT (modify/delete): numbers deleted" out
7392
'
7493

94+
test_expect_success '--quiet on conflicted merge' '
95+
# Get rid of loose objects to start with
96+
git gc &&
97+
echo "0 objects, 0 kilobytes" >expect &&
98+
git count-objects >actual &&
99+
test_cmp expect actual &&
100+
101+
# Ensure merge has conflict
102+
test_expect_code 1 git merge-tree --write-tree --quiet side1 side2 >output &&
103+
104+
# Ensure there is no output
105+
test_must_be_empty output &&
106+
107+
# Ensure no loose objects written (all new objects written would have
108+
# been in "outer layer" of the merge)
109+
git count-objects >actual &&
110+
test_cmp expect actual
111+
'
112+
75113
test_expect_success 'Content merge and a few conflicts' '
76114
git checkout side1^0 &&
77115
test_must_fail git merge side2 &&

0 commit comments

Comments
 (0)