Skip to content

Commit 9142fce

Browse files
committed
Merge branch 'ah/rebase-merges-config'
Streamline --rebase-merges command line option handling and introduce rebase.merges configuration variable. * ah/rebase-merges-config: rebase: add a config option for --rebase-merges rebase: deprecate --rebase-merges="" rebase: add documentation and test for --no-rebase-merges
2 parents 7e13d65 + 6605fb7 commit 9142fce

File tree

5 files changed

+138
-22
lines changed

5 files changed

+138
-22
lines changed

Documentation/config/rebase.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
6767

6868
rebase.forkPoint::
6969
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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -529,20 +529,25 @@ See also INCOMPATIBLE OPTIONS below.
529529

530530
-r::
531531
--rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
532+
--no-rebase-merges::
532533
By default, a rebase will simply drop merge commits from the todo
533534
list, and put the rebased commits into a single, linear branch.
534535
With `--rebase-merges`, the rebase will instead try to preserve
535536
the branching structure within the commits that are to be rebased,
536537
by recreating the merge commits. Any resolved merge conflicts or
537538
manual amendments in these merge commits will have to be
538-
resolved/re-applied manually.
539+
resolved/re-applied manually. `--no-rebase-merges` can be used to
540+
countermand both the `rebase.rebaseMerges` config option and a previous
541+
`--rebase-merges`.
539542
+
540-
By default, or when `no-rebase-cousins` was specified, commits which do not
541-
have `<upstream>` as direct ancestor will keep their original branch point,
542-
i.e. commits that would be excluded by linkgit:git-log[1]'s
543-
`--ancestry-path` option will keep their original ancestry by default. If
544-
the `rebase-cousins` mode is turned on, such commits are instead rebased
545-
onto `<upstream>` (or `<onto>`, if specified).
543+
When rebasing merges, there are two modes: `rebase-cousins` and
544+
`no-rebase-cousins`. If the mode is not specified, it defaults to
545+
`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
546+
`<upstream>` as direct ancestor will keep their original branch point, i.e.
547+
commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
548+
option will keep their original ancestry by default. In `rebase-cousins` mode,
549+
such commits are instead rebased onto `<upstream>` (or `<onto>`, if
550+
specified).
546551
+
547552
It is currently only possible to recreate the merge commits using the
548553
`ort` merge strategy; different merge strategies can be used only via

builtin/rebase.c

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ struct rebase_options {
124124
int fork_point;
125125
int update_refs;
126126
int config_autosquash;
127+
int config_rebase_merges;
127128
int config_update_refs;
128129
};
129130

@@ -141,6 +142,8 @@ struct rebase_options {
141142
.allow_empty_message = 1, \
142143
.autosquash = -1, \
143144
.config_autosquash = -1, \
145+
.rebase_merges = -1, \
146+
.config_rebase_merges = -1, \
144147
.update_refs = -1, \
145148
.config_update_refs = -1, \
146149
}
@@ -772,6 +775,16 @@ static int run_specific_rebase(struct rebase_options *opts)
772775
return status ? -1 : 0;
773776
}
774777

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+
775788
static int rebase_config(const char *var, const char *value, void *data)
776789
{
777790
struct rebase_options *opts = data;
@@ -801,6 +814,17 @@ static int rebase_config(const char *var, const char *value, void *data)
801814
return 0;
802815
}
803816

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+
804828
if (!strcmp(var, "rebase.updaterefs")) {
805829
opts->config_update_refs = git_config_bool(var, value);
806830
return 0;
@@ -981,6 +1005,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
9811005
return 0;
9821006
}
9831007

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+
9841030
static void NORETURN error_on_missing_default_upstream(void)
9851031
{
9861032
struct branch *current_branch = branch_get(NULL);
@@ -1036,7 +1082,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
10361082
struct object_id branch_base;
10371083
int ignore_whitespace = 0;
10381084
const char *gpg_sign = NULL;
1039-
const char *rebase_merges = NULL;
10401085
struct string_list strategy_options = STRING_LIST_INIT_NODUP;
10411086
struct object_id squash_onto;
10421087
char *squash_onto_name = NULL;
@@ -1138,10 +1183,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
11381183
&options.allow_empty_message,
11391184
N_("allow rebasing commits with empty messages"),
11401185
PARSE_OPT_HIDDEN),
1141-
{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
1142-
N_("mode"),
1186+
OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
11431187
N_("try to rebase merges instead of skipping them"),
1144-
PARSE_OPT_OPTARG, NULL, (intptr_t)""},
1188+
PARSE_OPT_OPTARG, parse_opt_rebase_merges),
11451189
OPT_BOOL(0, "fork-point", &options.fork_point,
11461190
N_("use 'merge-base --fork-point' to refine upstream")),
11471191
OPT_STRING('s', "strategy", &options.strategy,
@@ -1437,17 +1481,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
14371481
if (options.exec.nr)
14381482
imply_merge(&options, "--exec");
14391483

1440-
if (rebase_merges) {
1441-
if (!*rebase_merges)
1442-
; /* default mode; do nothing */
1443-
else if (!strcmp("rebase-cousins", rebase_merges))
1444-
options.rebase_cousins = 1;
1445-
else if (strcmp("no-rebase-cousins", rebase_merges))
1446-
die(_("Unknown mode: %s"), rebase_merges);
1447-
options.rebase_merges = 1;
1448-
imply_merge(&options, "--rebase-merges");
1449-
}
1450-
14511484
if (options.type == REBASE_APPLY) {
14521485
if (ignore_whitespace)
14531486
strvec_push(&options.git_am_opts,
@@ -1515,6 +1548,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
15151548
"cannot be used together"));
15161549
else if (options.autosquash == -1 && options.config_autosquash == 1)
15171550
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"));
15181553
else if (options.update_refs == -1 && options.config_update_refs == 1)
15191554
die(_("apply options are incompatible with rebase.updateRefs. Consider adding --no-update-refs"));
15201555
else
@@ -1527,6 +1562,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
15271562
options.update_refs = (options.update_refs >= 0) ? options.update_refs :
15281563
((options.config_update_refs >= 0) ? options.config_update_refs : 0);
15291564

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+
15301570
if (options.autosquash == 1)
15311571
imply_merge(&options, "--autosquash");
15321572
options.autosquash = (options.autosquash >= 0) ? options.autosquash :

t/t3422-rebase-incompatible-options.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ test_rebase_am_only () {
8585
test_must_fail git rebase $opt --reapply-cherry-picks A
8686
"
8787

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+
8893
test_expect_success "$opt incompatible with --update-refs" "
8994
git checkout B^0 &&
9095
test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
101106
grep -e --no-autosquash err
102107
"
103108

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+
104115
test_expect_success "$opt incompatible with rebase.updateRefs" "
105116
git checkout B^0 &&
106117
test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
113124
git -c rebase.autosquash=true rebase --no-autosquash $opt A
114125
"
115126

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+
116133
test_expect_success "$opt okay with overridden rebase.updateRefs" "
117134
test_when_finished \"git reset --hard B^0\" &&
118135
git checkout B^0 &&

t/t3430-rebase-merges.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
250250
EOF
251251
'
252252

253+
test_expect_success '--no-rebase-merges countermands --rebase-merges' '
254+
git checkout -b no-rebase-merges E &&
255+
git rebase --rebase-merges --no-rebase-merges C &&
256+
test_cmp_graph C.. <<-\EOF
257+
* B
258+
* D
259+
o C
260+
EOF
261+
'
262+
253263
test_expect_success 'do not rebase cousins unless asked for' '
254264
git checkout -b cousins main &&
255265
before="$(git rev-parse --verify HEAD)" &&
@@ -268,6 +278,40 @@ test_expect_success 'do not rebase cousins unless asked for' '
268278
EOF
269279
'
270280

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+
271315
test_expect_success 'refs/rewritten/* is worktree-local' '
272316
git worktree add wt &&
273317
cat >wt/script-from-scratch <<-\EOF &&

0 commit comments

Comments
 (0)