merge-tree: add a new --quiet flag

Git Forges may be interested in whether two branches can be merged while not being interested in what the resulting merge tree is nor which files conflicted. For such cases, add a new --quiet flag which will make use of the new mergeability_only flag added to merge-ort in the previous commit. This option allows the merge machinery to, in the outer layer of the merge: * exit early when a conflict is detected * avoid writing (most) merged blobs/trees to the object store Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed May 16, 2025 at 20:04 UTC 29d7bf19512d8ca97be5cf708ca2e0bcc29408ab
3 files changed +62
Documentation/git-merge-tree.adoc
+6
@@ -65,6 +65,12 @@ OPTIONS
65 default is to include these messages if there are merge
66 conflicts, and to omit them otherwise.
67
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 +
74 --allow-unrelated-histories::
75 merge-tree will by default error out if the two branches specified
76 share no common history. This flag can be given to override that
builtin/merge-tree.c
+18
@@ -490,6 +490,9 @@ static int real_merge(struct merge_tree_options *o,
490 if (result.clean < 0)
491 die(_("failure to merge"));
492
493 + if (o->merge_options.mergeability_only)
494 + goto cleanup;
495 +
496 if (show_messages == -1)
497 show_messages = !result.clean;
498
@@ -522,6 +525,8 @@ static int real_merge(struct merge_tree_options *o,
525 }
526 if (o->use_stdin)
527 putchar(line_termination);
528 +
529 +cleanup:
530 merge_finalize(&opt, &result);
531 clear_merge_options(&opt);
532 return !result.clean; /* result.clean < 0 handled above */
@@ -538,6 +543,7 @@ int cmd_merge_tree(int argc,
543 int original_argc;
544 const char *merge_base = NULL;
545 int ret;
546 + int quiet = 0;
547
548 const char * const merge_tree_usage[] = {
549 N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
@@ -552,6 +558,10 @@ int cmd_merge_tree(int argc,
558 N_("do a trivial merge only"), MODE_TRIVIAL),
559 OPT_BOOL(0, "messages", &o.show_messages,
560 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),
565 OPT_SET_INT('z', NULL, &line_termination,
566 N_("separate paths with the NUL character"), '\0'),
567 OPT_BOOL_F(0, "name-only",
@@ -583,6 +593,14 @@ int cmd_merge_tree(int argc,
593 argc = parse_options(argc, argv, prefix, mt_options,
594 merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
595
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 +
604 if (xopts.nr && o.mode == MODE_TRIVIAL)
605 die(_("--trivial-merge is incompatible with all other options"));
606 for (size_t x = 0; x < xopts.nr; x++)
t/t4301-merge-tree-write-tree.sh
+38
@@ -54,6 +54,25 @@ test_expect_success setup '
54 git commit -m first-commit
55 '
56
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 +
76 test_expect_success 'Clean merge' '
77 TREE_OID=$(git merge-tree --write-tree side1 side3) &&
78 q_to_tab <<-EOF >expect &&
@@ -72,6 +91,25 @@ test_expect_success 'Failed merge without rename detection' '
91 grep "CONFLICT (modify/delete): numbers deleted" out
92 '
93
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 +
113 test_expect_success 'Content merge and a few conflicts' '
114 git checkout side1^0 &&
115 test_must_fail git merge side2 &&