rebase: add a config option for --rebase-merges

The purpose of the new option is to accommodate users who would like --rebase-merges to be on by default and to facilitate turning on --rebase-merges by default without configuration in a future version of Git. Name the new option rebase.rebaseMerges, even though it is a little redundant, for consistency with the name of the command line option and to be clear when scrolling through values in the [rebase] section of .gitconfig. Support setting rebase.rebaseMerges to the nonspecific value "true" for users who don't need to or don't want to learn about the difference between rebase-cousins and no-rebase-cousins. Make --rebase-merges without an argument on the command line override any value of rebase.rebaseMerges in the configuration, for consistency with other command line flags with optional arguments that have an associated config option. Signed-off-by: Alex Henrie <alexhenrie24@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Alex Henrie committed Mar 25, 2023 at 21:06 UTC 6605fb70cb79776af9e20c0300dc66563a300b42
5 files changed +118 -20
Documentation/config/rebase.txt
+10
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
67
68 rebase.forkPoint::
69 If set to false set `--no-fork-point` option by default.
70 +
71 +rebase.rebaseMerges::
72 + Whether and how to set the `--rebase-merges` option by default. Can
73 + be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
74 + true or to `no-rebase-cousins` is equivalent to
75 + `--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
76 + equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
77 + equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
78 + command line, with or without an argument, overrides any
79 + `rebase.rebaseMerges` configuration.
Documentation/git-rebase.txt
+2 -1
@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
537 by recreating the merge commits. Any resolved merge conflicts or
538 manual amendments in these merge commits will have to be
539 resolved/re-applied manually. `--no-rebase-merges` can be used to
540 - countermand a previous `--rebase-merges`.
540 + countermand both the `rebase.rebaseMerges` config option and a previous
541 + `--rebase-merges`.
542 +
543 When rebasing merges, there are two modes: `rebase-cousins` and
544 `no-rebase-cousins`. If the mode is not specified, it defaults to
builtin/rebase.c
+55 -19
@@ -124,6 +124,7 @@ struct rebase_options {
124 int fork_point;
125 int update_refs;
126 int config_autosquash;
127 + int config_rebase_merges;
128 int config_update_refs;
129 };
130
@@ -141,6 +142,8 @@ struct rebase_options {
142 .allow_empty_message = 1, \
143 .autosquash = -1, \
144 .config_autosquash = -1, \
145 + .rebase_merges = -1, \
146 + .config_rebase_merges = -1, \
147 .update_refs = -1, \
148 .config_update_refs = -1, \
149 }
@@ -772,6 +775,16 @@ static int run_specific_rebase(struct rebase_options *opts)
775 return status ? -1 : 0;
776 }
777
778 +static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
779 +{
780 + if (!strcmp("no-rebase-cousins", value))
781 + options->rebase_cousins = 0;
782 + else if (!strcmp("rebase-cousins", value))
783 + options->rebase_cousins = 1;
784 + else
785 + die(_("Unknown rebase-merges mode: %s"), value);
786 +}
787 +
788 static int rebase_config(const char *var, const char *value, void *data)
789 {
790 struct rebase_options *opts = data;
@@ -801,6 +814,17 @@ static int rebase_config(const char *var, const char *value, void *data)
814 return 0;
815 }
816
817 + if (!strcmp(var, "rebase.rebasemerges")) {
818 + opts->config_rebase_merges = git_parse_maybe_bool(value);
819 + if (opts->config_rebase_merges < 0) {
820 + opts->config_rebase_merges = 1;
821 + parse_rebase_merges_value(opts, value);
822 + } else {
823 + opts->rebase_cousins = 0;
824 + }
825 + return 0;
826 + }
827 +
828 if (!strcmp(var, "rebase.updaterefs")) {
829 opts->config_update_refs = git_config_bool(var, value);
830 return 0;
@@ -981,6 +1005,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
1005 return 0;
1006 }
1007
1008 +static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
1009 +{
1010 + struct rebase_options *options = opt->value;
1011 +
1012 + options->rebase_merges = !unset;
1013 + options->rebase_cousins = 0;
1014 +
1015 + if (arg) {
1016 + if (!*arg) {
1017 + warning(_("--rebase-merges with an empty string "
1018 + "argument is deprecated and will stop "
1019 + "working in a future version of Git. Use "
1020 + "--rebase-merges without an argument "
1021 + "instead, which does the same thing."));
1022 + return 0;
1023 + }
1024 + parse_rebase_merges_value(options, arg);
1025 + }
1026 +
1027 + return 0;
1028 +}
1029 +
1030 static void NORETURN error_on_missing_default_upstream(void)
1031 {
1032 struct branch *current_branch = branch_get(NULL);
@@ -1036,7 +1082,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1082 struct object_id branch_base;
1083 int ignore_whitespace = 0;
1084 const char *gpg_sign = NULL;
1039 - const char *rebase_merges = NULL;
1085 struct string_list strategy_options = STRING_LIST_INIT_NODUP;
1086 struct object_id squash_onto;
1087 char *squash_onto_name = NULL;
@@ -1138,10 +1183,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1183 &options.allow_empty_message,
1184 N_("allow rebasing commits with empty messages"),
1185 PARSE_OPT_HIDDEN),
1141 - {OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
1142 - N_("mode"),
1186 + OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
1187 N_("try to rebase merges instead of skipping them"),
1144 - PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
1188 + PARSE_OPT_OPTARG, parse_opt_rebase_merges),
1189 OPT_BOOL(0, "fork-point", &options.fork_point,
1190 N_("use 'merge-base --fork-point' to refine upstream")),
1191 OPT_STRING('s', "strategy", &options.strategy,
@@ -1437,21 +1481,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1481 if (options.exec.nr)
1482 imply_merge(&options, "--exec");
1483
1440 - if (rebase_merges) {
1441 - if (!*rebase_merges)
1442 - warning(_("--rebase-merges with an empty string "
1443 - "argument is deprecated and will stop "
1444 - "working in a future version of Git. Use "
1445 - "--rebase-merges without an argument "
1446 - "instead, which does the same thing."));
1447 - else if (!strcmp("rebase-cousins", rebase_merges))
1448 - options.rebase_cousins = 1;
1449 - else if (strcmp("no-rebase-cousins", rebase_merges))
1450 - die(_("Unknown mode: %s"), rebase_merges);
1451 - options.rebase_merges = 1;
1452 - imply_merge(&options, "--rebase-merges");
1453 - }
1454 -
1484 if (options.type == REBASE_APPLY) {
1485 if (ignore_whitespace)
1486 strvec_push(&options.git_am_opts,
@@ -1519,6 +1548,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1548 "cannot be used together"));
1549 else if (options.autosquash == -1 && options.config_autosquash == 1)
1550 die(_("apply options are incompatible with rebase.autoSquash. Consider adding --no-autosquash"));
1551 + else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
1552 + die(_("apply options are incompatible with rebase.rebaseMerges. Consider adding --no-rebase-merges"));
1553 else if (options.update_refs == -1 && options.config_update_refs == 1)
1554 die(_("apply options are incompatible with rebase.updateRefs. Consider adding --no-update-refs"));
1555 else
@@ -1531,6 +1562,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1562 options.update_refs = (options.update_refs >= 0) ? options.update_refs :
1563 ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
1564
1565 + if (options.rebase_merges == 1)
1566 + imply_merge(&options, "--rebase-merges");
1567 + options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
1568 + ((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
1569 +
1570 if (options.autosquash == 1)
1571 imply_merge(&options, "--autosquash");
1572 options.autosquash = (options.autosquash >= 0) ? options.autosquash :
t/t3422-rebase-incompatible-options.sh
+17
@@ -85,6 +85,11 @@ test_rebase_am_only () {
85 test_must_fail git rebase $opt --reapply-cherry-picks A
86 "
87
88 + test_expect_success "$opt incompatible with --rebase-merges" "
89 + git checkout B^0 &&
90 + test_must_fail git rebase $opt --rebase-merges A
91 + "
92 +
93 test_expect_success "$opt incompatible with --update-refs" "
94 git checkout B^0 &&
95 test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
106 grep -e --no-autosquash err
107 "
108
109 + test_expect_success "$opt incompatible with rebase.rebaseMerges" "
110 + git checkout B^0 &&
111 + test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
112 + grep -e --no-rebase-merges err
113 + "
114 +
115 test_expect_success "$opt incompatible with rebase.updateRefs" "
116 git checkout B^0 &&
117 test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
124 git -c rebase.autosquash=true rebase --no-autosquash $opt A
125 "
126
127 + test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
128 + test_when_finished \"git reset --hard B^0\" &&
129 + git checkout B^0 &&
130 + git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
131 + "
132 +
133 test_expect_success "$opt okay with overridden rebase.updateRefs" "
134 test_when_finished \"git reset --hard B^0\" &&
135 git checkout B^0 &&
t/t3430-rebase-merges.sh
+34
@@ -278,6 +278,40 @@ test_expect_success 'do not rebase cousins unless asked for' '
278 EOF
279 '
280
281 +test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
282 + test_config rebase.rebaseMerges rebase-cousins &&
283 + git checkout -b config-rebase-cousins main &&
284 + git rebase HEAD^ &&
285 + test_cmp_graph HEAD^.. <<-\EOF
286 + * Merge the topic branch '\''onebranch'\''
287 + |\
288 + | * D
289 + | * G
290 + |/
291 + o H
292 + EOF
293 +'
294 +
295 +test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
296 + test_config rebase.rebaseMerges no-rebase-cousins &&
297 + git checkout -b override-config-no-rebase-cousins E &&
298 + git rebase --no-rebase-merges C &&
299 + test_cmp_graph C.. <<-\EOF
300 + * B
301 + * D
302 + o C
303 + EOF
304 +'
305 +
306 +test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
307 + test_config rebase.rebaseMerges rebase-cousins &&
308 + git checkout -b override-config-rebase-cousins E &&
309 + before="$(git rev-parse --verify HEAD)" &&
310 + test_tick &&
311 + git rebase --rebase-merges C &&
312 + test_cmp_rev HEAD $before
313 +'
314 +
315 test_expect_success 'refs/rewritten/* is worktree-local' '
316 git worktree add wt &&
317 cat >wt/script-from-scratch <<-\EOF &&