threads / patch / 59293

v4, 3 partsrebase: add documentation and test for --no-rebase-merges

Subject: [PATCH v4 1/3] rebase: add documentation and test for --no-rebase-merges

## tl;dr

96 messages between Feb 23, 2023 and Mar 27, 2023. Diffs are folded; open one to read it.

replies: 95people: 8as markdown or json

Alex Henrie· Feb 23, 2023, 05:34 UTC · lore
Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt |  4 +++-
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)
Show changes to 2 files +13 −1

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..c98784a0d2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,13 +529,15 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
 By default, or when `no-rebase-cousins` was specified, commits which do not
 have `<upstream>` as direct ancestor will keep their original branch point,
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.39.2
Alex Henrie· Feb 23, 2023, 05:34 UTC · re: Alex Henrie · lore

[PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid confusion when a rebase.merges config option is introduced, where rebase.merges="" will be equivalent to not passing --rebase-merges.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c         | 6 ++----
 t/t3430-rebase-merges.sh | 6 ++++++
 2 files changed, 8 insertions(+), 4 deletions(-)
Show changes to 2 files +8 −4

builtin/rebase.c, t/t3430-rebase-merges.sh

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6635f10d52..b68fc2fbb7 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1437,9 +1437,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		imply_merge(&options, "--exec");
 
 	if (rebase_merges) {
-		if (!*rebase_merges)
-			; /* default mode; do nothing */
-		else if (!strcmp("rebase-cousins", rebase_merges))
+		if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
 			die(_("Unknown mode: %s"), rebase_merges);
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..c73949df18 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,12 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success '--rebase-merges="" is invalid syntax' '
+	echo "fatal: Unknown mode: " >expect &&
+	test_must_fail git rebase --rebase-merges="" HEAD^ 2>actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Johannes Schindelin· Feb 24, 2023, 13:54 UTC · re: Alex Henrie · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

Hi Alex,
On Wed, 22 Feb 2023, Alex Henrie wrote:
Show 5 quoted lines
> The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
> empty string argument) has been an undocumented synonym of
> --rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid
> confusion when a rebase.merges config option is introduced, where
> rebase.merges="" will be equivalent to not passing --rebase-merges.

Being undocumented and obscure might be a good reason for some to consider this a bug; I do not. You could deprecate it, but there are probably better ideas than to remove it without prior warning.

If all you want to do is to support `rebase.merges=`, you can do that without having to change the meaning of `--rebase-merges=`.

Ciao, Johannes

Junio C Hamano· Feb 24, 2023, 17:20 UTC · re: Johannes Schindelin · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 14 quoted lines
> On Wed, 22 Feb 2023, Alex Henrie wrote:
>
>> The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
>> empty string argument) has been an undocumented synonym of
>> --rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid
>> confusion when a rebase.merges config option is introduced, where
>> rebase.merges="" will be equivalent to not passing --rebase-merges.
>
> Being undocumented and obscure might be a good reason for some to consider
> this a bug; I do not. You could deprecate it, but there are probably
> better ideas than to remove it without prior warning.
>
> If all you want to do is to support `rebase.merges=`, you can do that
> without having to change the meaning of `--rebase-merges=`.

Thanks for a doze of sanity. Let me mark the topic as on hold to wait for a resolution.

Alex Henrie· Feb 24, 2023, 17:50 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On Fri, Feb 24, 2023 at 10:20 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 20 quoted lines
>
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > On Wed, 22 Feb 2023, Alex Henrie wrote:
> >
> >> The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
> >> empty string argument) has been an undocumented synonym of
> >> --rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid
> >> confusion when a rebase.merges config option is introduced, where
> >> rebase.merges="" will be equivalent to not passing --rebase-merges.
> >
> > Being undocumented and obscure might be a good reason for some to consider
> > this a bug; I do not. You could deprecate it, but there are probably
> > better ideas than to remove it without prior warning.
> >
> > If all you want to do is to support `rebase.merges=`, you can do that
> > without having to change the meaning of `--rebase-merges=`.
>
> Thanks for a doze of sanity.  Let me mark the topic as on hold to
> wait for a resolution.

As Johannes pointed out, it's going to be confusing if rebase.merges="" does not mean rebase.merges=false. It's also going to be confusing if rebase.merges="" does not mean --rebase-merges="". The only sane option I see is to drop --rebase-merges="", or to add a deprecation warning now and drop it later.

-Alex
Junio C Hamano· Feb 24, 2023, 18:08 UTC · re: Alex Henrie · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 5 quoted lines
> As Johannes pointed out, it's going to be confusing if
> rebase.merges="" does not mean rebase.merges=false. It's also going to
> be confusing if rebase.merges="" does not mean --rebase-merges="". The
> only sane option I see is to drop --rebase-merges="", or to add a
> deprecation warning now and drop it later.

If we were doing anything, then I think the only sensible way forward is to warn and then drop long after everybody forgets about it.

But does it really need to be changed? I am perfectly happy to declare that those who wants to set rebase.merges to say false should set it to false (or no or 0), not an empty string.

Also, "[rebase] merges = " in the configuration files does not have to mean the same thing as "--rebase-merges=" from the command line. Can't we just reserve that strange "--rebase-merges=" given from the command line to those who are already using it, without even advertising it in the documentation, document and encourage the longhand "--rebase-merges=no-rebase-cousins" from the command line and take only the form that corresponds to it in the configuration file, i.e. "[rebase] merges = no-rebase-cousins". We could even error out "[rebase] merges = " in the configuration file, as nobody is using such a configuration variable today.

Alex Henrie· Feb 24, 2023, 18:23 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On Fri, Feb 24, 2023 at 11:08 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 27 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> > As Johannes pointed out, it's going to be confusing if
> > rebase.merges="" does not mean rebase.merges=false. It's also going to
> > be confusing if rebase.merges="" does not mean --rebase-merges="". The
> > only sane option I see is to drop --rebase-merges="", or to add a
> > deprecation warning now and drop it later.
>
> If we were doing anything, then I think the only sensible way
> forward is to warn and then drop long after everybody forgets about
> it.
>
> But does it really need to be changed?  I am perfectly happy to
> declare that those who wants to set rebase.merges to say false
> should set it to false (or no or 0), not an empty string.
>
> Also, "[rebase] merges = " in the configuration files does not have
> to mean the same thing as "--rebase-merges=" from the command line.
> Can't we just reserve that strange "--rebase-merges=" given from the
> command line to those who are already using it, without even
> advertising it in the documentation, document and encourage the
> longhand "--rebase-merges=no-rebase-cousins" from the command line
> and take only the  form that corresponds to it in the configuration
> file, i.e. "[rebase] merges = no-rebase-cousins".  We could even
> error out "[rebase] merges = " in the configuration file, as nobody
> is using such a configuration variable today.

The only way to truly make "[rebase] merges =" invalid is to print an error message and die with that configuration. I think that would be confusing too, especially since it's now looking like rebase.merges needs to be a pure boolean and an independent rebase.cousins boolean option is needed as well. However, if you think that that's better than deprecating --rebase-merges="" with the plan to drop it long after everybody forgets about it, then I'm happy to oblige. Just let me know what is wanted.

-Alex
Junio C Hamano· Feb 24, 2023, 18:40 UTC · re: Alex Henrie · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 5 quoted lines
> The only way to truly make "[rebase] merges =" invalid is to print an
> error message and die with that configuration. I think that would be
> confusing too, especially since it's now looking like rebase.merges
> needs to be a pure boolean and an independent rebase.cousins boolean
> option is needed as well.
Oh, I wasn't aware of that direction.

I do not know why rebase.cousins, which would only be meaningful when rebase.merges is true, is a better design than rebase.merges that is an enum of "don't do the merges stuff" plus "do the merges stuff with cousins", "without cousins" (which may allow us to gain more different ways to do "merges stuff" later), but that is what gained consensus on the list, then "[rebase]merges=" would become a problem.

But --rebase-merges from the command line is not a pure Boolean already, so what does "[rebase]merges" that is a pure Boolean aim to help?

Alex Henrie· Feb 24, 2023, 18:55 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On Fri, Feb 24, 2023 at 11:40 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 22 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> > The only way to truly make "[rebase] merges =" invalid is to print an
> > error message and die with that configuration. I think that would be
> > confusing too, especially since it's now looking like rebase.merges
> > needs to be a pure boolean and an independent rebase.cousins boolean
> > option is needed as well.
>
> Oh, I wasn't aware of that direction.
>
> I do not know why rebase.cousins, which would only be meaningful
> when rebase.merges is true, is a better design than rebase.merges
> that is an enum of "don't do the merges stuff" plus "do the merges
> stuff with cousins", "without cousins" (which may allow us to gain
> more different ways to do "merges stuff" later), but that is what
> gained consensus on the list, then "[rebase]merges=" would become a
> problem.
>
> But --rebase-merges from the command line is not a pure Boolean
> already, so what does "[rebase]merges" that is a pure Boolean aim to
> help?

Phillip is concerned about people and scripts assuming that --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins, see [1].

Tao and others are probably not going to like it if --rebase-merges without an argument undoes a rebase.merges=rebase-cousins configuration.

It seems to me that the only way to make everyone happy is to have separate rebase.merges and rebase.cousins options. You have a point that separating the options could cause problems if --rebase-merges starts accepting more arguments in the future, but if that happens we could deal with it by adding more possible values to rebase.cousins or introducing a third config option.

-Alex
[1] https://lore.kernel.org/git/CAMMLpeQ98BTCGE2tcVdZ99eU6cLh4Rd_hc8C_PmKvsBkjXUWPw@mail.gmail.com/
Junio C Hamano· Feb 24, 2023, 19:13 UTC · re: Alex Henrie · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

Alex Henrie <alexhenrie24@gmail.com> writes:
> Phillip is concerned about people and scripts assuming that
> --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins,
> see [1].

Isn't that already broken when you introduce rebase.merges configuration? People and scripts are already relying on the lack of rebase-merges to flatten, and script writers will be surprised to receive a "bug report" complaining that their script does not work when the users set rebase.merges to anything but no.

> Tao and others are probably not going to like it if --rebase-merges
> without an argument undoes a rebase.merges=rebase-cousins
> configuration.

That is why I suggested to keep --rebase-merges= (with no value or an empty string) only for those who came from the world where it defaults to no-rebase-cousins and there was no rebase.merges configuration. If --rebase-merges= is given from the command line without value *and* rebase.merges configuration is there (which is Tao's concern?), the command line option can error out asking for an explicit value to countermand whatever value is configured.

Wouldn't that work for folks from both camps?
Alex Henrie· Feb 24, 2023, 19:24 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On Fri, Feb 24, 2023 at 12:13 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> > Phillip is concerned about people and scripts assuming that
> > --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins,
> > see [1].
>
> Isn't that already broken when you introduce rebase.merges
> configuration?  People and scripts are already relying on the lack
> of rebase-merges to flatten, and script writers will be surprised to
> receive a "bug report" complaining that their script does not work
> when the users set rebase.merges to anything but no.

Yeah, I don't know why breaking the assumption that --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins is any worse than breaking the assumption that not passing --rebase-merges is equivalent to passing --no-rebase-merges. And the assumption is broken whether one new config option is introduced or two, so splitting the option up probably wouldn't make Phillip any happier.

Show 13 quoted lines
> > Tao and others are probably not going to like it if --rebase-merges
> > without an argument undoes a rebase.merges=rebase-cousins
> > configuration.
>
> That is why I suggested to keep --rebase-merges= (with no value or
> an empty string) only for those who came from the world where it
> defaults to no-rebase-cousins and there was no rebase.merges
> configuration.  If --rebase-merges= is given from the command line
> without value *and* rebase.merges configuration is there (which is
> Tao's concern?), the command line option can error out asking for an
> explicit value to countermand whatever value is configured.
>
> Wouldn't that work for folks from both camps?

Maybe. I still don't like the idea of --rebase-merges="" ever being not equivalent to rebase.merges="".

-Alex
Phillip Wood· Feb 24, 2023, 19:24 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On 24/02/2023 19:13, Junio C Hamano wrote:
Show 8 quoted lines
> Alex Henrie <alexhenrie24@gmail.com> writes:
> 
>> Phillip is concerned about people and scripts assuming that
>> --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins,
>> see [1].
> 
> Isn't that already broken when you introduce rebase.merges
> configuration?

Scripts using --rebase-merges are not broken by the introduction of rebase.merges so long as we follow our usual convention of always allowing the commandline to override the config (i.e. --rebase-merges is always equivalent to --rebase-merges=no-rebase-cousins). I don't really understand why Alex is suggesting splitting the config into two based on my comments.

> People and scripts are already relying on the lack
> of rebase-merges to flatten, and script writers will be surprised to
> receive a "bug report" complaining that their script does not work
> when the users set rebase.merges to anything but no.
That is true.
Best Wishes
Phillip
Show 13 quoted lines
>> Tao and others are probably not going to like it if --rebase-merges
>> without an argument undoes a rebase.merges=rebase-cousins
>> configuration.
> 
> That is why I suggested to keep --rebase-merges= (with no value or
> an empty string) only for those who came from the world where it
> defaults to no-rebase-cousins and there was no rebase.merges
> configuration.  If --rebase-merges= is given from the command line
> without value *and* rebase.merges configuration is there (which is
> Tao's concern?), the command line option can error out asking for an
> explicit value to countermand whatever value is configured.
> 
> Wouldn't that work for folks from both camps?
Alex Henrie· Feb 24, 2023, 19:56 UTC · re: Phillip Wood · lore

Re: [PATCH v4 2/3] rebase: stop accepting --rebase-merges=""

On Fri, Feb 24, 2023 at 12:24 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 17 quoted lines
>
> On 24/02/2023 19:13, Junio C Hamano wrote:
> > Alex Henrie <alexhenrie24@gmail.com> writes:
> >
> >> Phillip is concerned about people and scripts assuming that
> >> --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins,
> >> see [1].
> >
> > Isn't that already broken when you introduce rebase.merges
> > configuration?
>
> Scripts using --rebase-merges are not broken by the introduction of
> rebase.merges so long as we follow our usual convention of always
> allowing the commandline to override the config (i.e. --rebase-merges is
> always equivalent to --rebase-merges=no-rebase-cousins). I don't really
> understand why Alex is suggesting splitting the config into two based on
> my comments.

I was thinking that it would be less surprising to users if the option that broke the no-rebase-cousins assumption had "cousins" in its name. I should have stopped to think that that wouldn't really address your concern because regardless of what the option is named, it could still result in surprising behavior. I apologize for the unhelpful suggestion.

Show 6 quoted lines
> > People and scripts are already relying on the lack
> > of rebase-merges to flatten, and script writers will be surprised to
> > receive a "bug report" complaining that their script does not work
> > when the users set rebase.merges to anything but no.
>
> That is true.

In addition to specifying --no-rebase-merges rather than assuming it, shouldn't the "usual convention" for writing scripts also include being explicit about --rebase-merges=rebase-cousins or --rebase-merges=no-rebase-cousins? And if that is the case, is it really much of a loss to let rebase.merges=rebase-cousins override --rebase-merges without an argument?

-Alex
Alex Henrie· Feb 23, 2023, 05:34 UTC · re: Alex Henrie · lore

[PATCH v4 3/3] 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 possibly turning on --rebase-merges by default without configuration in a future version of Git.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/config/rebase.txt | 10 ++++
 Documentation/git-rebase.txt    |  3 +-
 builtin/rebase.c                | 47 ++++++++++++----
 t/t3430-rebase-merges.sh        | 96 +++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 12 deletions(-)
Show changes to 4 files +144 −12

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..308baa9dbb 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.merges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true is equivalent to `--rebase-merges` without an argument, setting to
+	`rebase-cousins` or `no-rebase-cousins` is equivalent to
+	`--rebase-merges` with that value as its argument, and setting to false
+	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line without an argument overrides a `rebase.merges=false`
+	configuration but does not override other values of `rebase.merge`.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c98784a0d2..b02f9cbb8c 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.merges` config option and a previous
+	`--rebase-merges`.
 +
 By default, or when `no-rebase-cousins` was specified, commits which do not
 have `<upstream>` as direct ancestor will keep their original branch point,
diff --git a/builtin/rebase.c b/builtin/rebase.c
index b68fc2fbb7..45cf445d42 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -771,6 +771,20 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_merges_value(struct rebase_options *options, const char *value)
+{
+	if (value) {
+		if (!strcmp("no-rebase-cousins", value))
+			options->rebase_cousins = 0;
+		else if (!strcmp("rebase-cousins", value))
+			options->rebase_cousins = 1;
+		else
+			die(_("Unknown mode: %s"), value);
+	}
+
+	options->rebase_merges = 1;
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -815,6 +829,13 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.merges") && value && *value) {
+		opts->rebase_merges = git_parse_maybe_bool(value);
+		if (opts->rebase_merges < 0)
+			parse_merges_value(opts, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.backend")) {
 		return git_config_string(&opts->default_backend, var, value);
 	}
@@ -980,6 +1001,18 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	if (unset)
+		options->rebase_merges = 0;
+	else
+		parse_merges_value(options, arg);
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1035,7 +1068,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1137,10 +1169,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1436,14 +1467,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
+	if (options.rebase_merges)
 		imply_merge(&options, "--rebase-merges");
-	}
 
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index c73949df18..d4b0e8fd49 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -284,6 +284,102 @@ test_expect_success '--rebase-merges="" is invalid syntax' '
 	test_cmp expect actual
 '
 
+test_expect_success 'rebase.merges="" is equivalent to not passing --rebase-merges' '
+	test_config rebase.merges "" &&
+	git checkout -b config-merges-blank E &&
+	git rebase C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
+	test_config rebase.merges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b override-config-rebase-cousins main &&
+	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	o | H
+	|/
+	o A
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.merges=false' '
+	test_config rebase.merges false &&
+	git checkout -b override-config-merges-false E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
+test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b no-override-config-rebase-cousins main &&
+	git rebase --rebase-merges HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success 'local rebase.merges=false overrides global rebase.merges=true' '
+	test_config_global rebase.merges true &&
+	test_config rebase.merges false &&
+	git checkout -b override-global-config E &&
+	git rebase C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success 'local rebase.merges="" does not override global rebase.merges=true' '
+	test_config_global rebase.merges no-rebase-cousins &&
+	test_config rebase.merges "" &&
+	git checkout -b no-override-global-config E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase C &&
+	test_cmp_rev HEAD $before
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Johannes Schindelin· Feb 24, 2023, 13:53 UTC · re: Alex Henrie · lore

Re: [PATCH v4 3/3] rebase: add a config option for --rebase-merges

Hi Alex,
On Wed, 22 Feb 2023, Alex Henrie wrote:
Show 12 quoted lines
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index b68fc2fbb7..45cf445d42 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -771,6 +771,20 @@ static int run_specific_rebase(struct rebase_options *opts)
>  	return status ? -1 : 0;
>  }
>
> +static void parse_merges_value(struct rebase_options *options, const char *value)
> +{
> +	if (value) {
> +		if (!strcmp("no-rebase-cousins", value))

If you want to support `rebase.merges=` to imply `no-rebase-cousins`, this would be the correct place to do it:

		if (!*value || !strcmp("no-rebase-cousins", value))
Show 8 quoted lines
> +			options->rebase_cousins = 0;
> +		else if (!strcmp("rebase-cousins", value))
> +			options->rebase_cousins = 1;
> +		else
> +			die(_("Unknown mode: %s"), value);
> +	}
> +
> +	options->rebase_merges = 1;

I would expect `options->rebase_merges = 0` if `value == NULL`. IOW I would have expected `parse_merges_value()` to start with:

	if (!value) {
		options->rebase_merges = 0;
		return;
	}

This assumes, of course, the parse_options semantics, where a `--no-*` option passes `NULL` as argument to the callback.

However, this is _not_ the parse_options callback, and if the (optional) argument was not specified, we do end up with a `NULL` here in spite of wanting to enable the rebase-merges mode.

However, a primary reason why you introduce the function is to support config value parsing. And in config value parsing, a "maybe-bool" with a NULL value is considered to be equivalent to `true`! (See `git_parse_maybe_bool_text()` or https://git-scm.com/docs/git-config#Documentation/git-config.txt-true for details.). For example,

	[http]
		sslVerify
is equivalent to
	[http]
		sslVerify = true

But since `git_parse_maybe_bool()` already takes care of handling that case (in which case we do not even want to call `git_parse_maybe_bool()`), you can limit that function to handling the command-line semantics.

So with those confusingly disagreeing semantics, I see not only myself, but other readers doing very, very well, indeed, with a code comment that explains under what circumstances we expect this callback to be called with `value == NULL`.

Show 10 quoted lines
> +}
> +
>  static int rebase_config(const char *var, const char *value, void *data)
>  {
>  	struct rebase_options *opts = data;
> @@ -815,6 +829,13 @@ static int rebase_config(const char *var, const char *value, void *data)
>  		return 0;
>  	}
>
> +	if (!strcmp(var, "rebase.merges") && value && *value) {
Why do we require a non-empty `value` here?
	[rebase]
		merges
should be equivalent to `true`,
	[rebase]
		merges =

should probably be equivalent to `false`, and both are handled correctly by `git_parse_maybe_bool()`.

Show 25 quoted lines
> +		opts->rebase_merges = git_parse_maybe_bool(value);
> +		if (opts->rebase_merges < 0)
> +			parse_merges_value(opts, value);
> +		return 0;
> +	}
> +
>  	if (!strcmp(var, "rebase.backend")) {
>  		return git_config_string(&opts->default_backend, var, value);
>  	}
> @@ -980,6 +1001,18 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>  	return 0;
>  }
>
> +static int parse_opt_merges(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +
> +	if (unset)
> +		options->rebase_merges = 0;
> +	else
> +		parse_merges_value(options, arg);
> +
> +	return 0;
> +}
> +

It is kind of inelegant to require a _second_ callback for the command-line parsing, but I guess if we want a `--no-rebase-merges` option to override a config setting, we cannot help it.

Show 144 quoted lines
>  static void NORETURN error_on_missing_default_upstream(void)
>  {
>  	struct branch *current_branch = branch_get(NULL);
> @@ -1035,7 +1068,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	struct object_id branch_base;
>  	int ignore_whitespace = 0;
>  	const char *gpg_sign = NULL;
> -	const char *rebase_merges = NULL;
>  	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
>  	struct object_id squash_onto;
>  	char *squash_onto_name = NULL;
> @@ -1137,10 +1169,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  			   &options.allow_empty_message,
>  			   N_("allow rebasing commits with empty messages"),
>  			   PARSE_OPT_HIDDEN),
> -		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
> -			N_("mode"),
> +		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
>  			N_("try to rebase merges instead of skipping them"),
> -			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
> +			PARSE_OPT_OPTARG, parse_opt_merges),
>  		OPT_BOOL(0, "fork-point", &options.fork_point,
>  			 N_("use 'merge-base --fork-point' to refine upstream")),
>  		OPT_STRING('s', "strategy", &options.strategy,
> @@ -1436,14 +1467,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	if (options.exec.nr)
>  		imply_merge(&options, "--exec");
>
> -	if (rebase_merges) {
> -		if (!strcmp("rebase-cousins", rebase_merges))
> -			options.rebase_cousins = 1;
> -		else if (strcmp("no-rebase-cousins", rebase_merges))
> -			die(_("Unknown mode: %s"), rebase_merges);
> -		options.rebase_merges = 1;
> +	if (options.rebase_merges)
>  		imply_merge(&options, "--rebase-merges");
> -	}
>
>  	if (options.type == REBASE_APPLY) {
>  		if (ignore_whitespace)
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
> index c73949df18..d4b0e8fd49 100755
> --- a/t/t3430-rebase-merges.sh
> +++ b/t/t3430-rebase-merges.sh
> @@ -284,6 +284,102 @@ test_expect_success '--rebase-merges="" is invalid syntax' '
>  	test_cmp expect actual
>  '
>
> +test_expect_success 'rebase.merges="" is equivalent to not passing --rebase-merges' '
> +	test_config rebase.merges "" &&
> +	git checkout -b config-merges-blank E &&
> +	git rebase C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b config-rebase-cousins main &&
> +	git rebase HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
> +test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
> +	test_config rebase.merges no-rebase-cousins &&
> +	git checkout -b override-config-no-rebase-cousins E &&
> +	git rebase --no-rebase-merges C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b override-config-rebase-cousins main &&
> +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	o | H
> +	|/
> +	o A
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
> +	test_config rebase.merges false &&
> +	git checkout -b override-config-merges-false E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase --rebase-merges C &&
> +	test_cmp_rev HEAD $before
> +'
> +
> +test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b no-override-config-rebase-cousins main &&
> +	git rebase --rebase-merges HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
> +test_expect_success 'local rebase.merges=false overrides global rebase.merges=true' '
> +	test_config_global rebase.merges true &&
> +	test_config rebase.merges false &&
> +	git checkout -b override-global-config E &&
> +	git rebase C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success 'local rebase.merges="" does not override global rebase.merges=true' '
> +	test_config_global rebase.merges no-rebase-cousins &&
> +	test_config rebase.merges "" &&
> +	git checkout -b no-override-global-config E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase C &&
> +	test_cmp_rev HEAD $before
> +'
> +

I understand the temptation to introduce exhaustive matrices that test all the different settings in all the different ways they can be specified.

However, I would much prefer to keep the tests succinct, not the least to avoid the every-increasing runtime of Git's CI. It's already taking about an order of magnitude or two too long to be reasonable.

So I'd suggest reducing the tests to a single one instead of eight: verify that `rebase.merges=no-rebase-cousins` is heeded, and that `--no-rebase-cousins` overrides that. That should be plenty sufficient to prevent regressions.

Ciao, Johannes

Show 7 quoted lines
>  test_expect_success 'refs/rewritten/* is worktree-local' '
>  	git worktree add wt &&
>  	cat >wt/script-from-scratch <<-\EOF &&
> --
> 2.39.2
>
>
Alex Henrie· Feb 24, 2023, 17:49 UTC · re: Johannes Schindelin · lore

Re: [PATCH v4 3/3] rebase: add a config option for --rebase-merges

On Fri, Feb 24, 2023 at 6:53 AM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

Show 5 quoted lines
> in config value parsing, a "maybe-bool" with a
> NULL value is considered to be equivalent to `true`! (See
> `git_parse_maybe_bool_text()` or
> https://git-scm.com/docs/git-config#Documentation/git-config.txt-true for
> details.).
Show 16 quoted lines
> On Wed, 22 Feb 2023, Alex Henrie wrote:
>
> > +     if (!strcmp(var, "rebase.merges") && value && *value) {
>
> Why do we require a non-empty `value` here?
>
>         [rebase]
>                 merges
>
> should be equivalent to `true`,
>
>         [rebase]
>                 merges =
>
> should probably be equivalent to `false`, and both are handled correctly
> by `git_parse_maybe_bool()`.

I didn't know that there was already an established convention for what NULL and "" mean for boolean config values. I should have looked at the source code of git_parse_maybe_bool more carefully. That does change things because we're going to want to follow the established convention here.

-Alex
Phillip Wood· Feb 24, 2023, 14:55 UTC · re: Alex Henrie · lore

Re: [PATCH v4 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 23/02/2023 05:34, Alex Henrie wrote:
Show 31 quoted lines
> The purpose of the new option is to accommodate users who would like
> --rebase-merges to be on by default and to facilitate possibly turning
> on --rebase-merges by default without configuration in a future version
> of Git.
> 
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
>   Documentation/config/rebase.txt | 10 ++++
>   Documentation/git-rebase.txt    |  3 +-
>   builtin/rebase.c                | 47 ++++++++++++----
>   t/t3430-rebase-merges.sh        | 96 +++++++++++++++++++++++++++++++++
>   4 files changed, 144 insertions(+), 12 deletions(-)
> 
> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
> index f19bd0e040..308baa9dbb 100644
> --- a/Documentation/config/rebase.txt
> +++ b/Documentation/config/rebase.txt
> @@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
>   
>   rebase.forkPoint::
>   	If set to false set `--no-fork-point` option by default.
> +
> +rebase.merges::
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true is equivalent to `--rebase-merges` without an argument, setting to
> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
> +	`--rebase-merges` with that value as its argument, and setting to false
> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line without an argument overrides a `rebase.merges=false`
> +	configuration but does not override other values of `rebase.merge`.

Thanks for updating this, it is much clearer what the different settings mean now. I not sure if having the config setting override the default when the user passes --rebase-merges without an argument is a good idea.

Show 14 quoted lines
> [...]   
> +static void parse_merges_value(struct rebase_options *options, const char *value)
> +{
> +	if (value) {
> +		if (!strcmp("no-rebase-cousins", value))
> +			options->rebase_cousins = 0;
> +		else if (!strcmp("rebase-cousins", value))
> +			options->rebase_cousins = 1;
> +		else
> +			die(_("Unknown mode: %s"), value);
> +	}
> +
> +	options->rebase_merges = 1;
> +}

It's a shame we seem to have grown yet another callback since v2, I'm not sure we should need to add quite so much code just to support a new config option

 > [...]
Show 13 quoted lines
> @@ -1436,14 +1467,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	if (options.exec.nr)
>   		imply_merge(&options, "--exec");
>   
> -	if (rebase_merges) {
> -		if (!strcmp("rebase-cousins", rebase_merges))
> -			options.rebase_cousins = 1;
> -		else if (strcmp("no-rebase-cousins", rebase_merges))
> -			die(_("Unknown mode: %s"), rebase_merges);
> -		options.rebase_merges = 1;
> +	if (options.rebase_merges)
>   		imply_merge(&options, "--rebase-merges");
> -	}

As I have said before I really think this patch needs to follow the lead of eddfcd8ece (rebase: provide better error message for apply options vs. merge config, 2023-01-25) and provide an error message if --rebase-merges is enabled by rebase.merges and the user provides a command line option that requires the apply backend. So

	git -c rebase.merges=true rebase --whitespace=fix
would result in
	error: apply options are incompatible with rebase.merges.
	Consider adding --no-rebase-merges
Show 5 quoted lines
> [...]
> +test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b no-override-config-rebase-cousins main &&
> +	git rebase --rebase-merges HEAD^ &&

I think this behavior is confusing for users and will break scripts that quite reasonably assume --rebase-merges is equivalent to --rebase-merges=no-rebase-cousins

Show 22 quoted lines
> [...]
> +test_expect_success 'local rebase.merges=false overrides global rebase.merges=true' '
> +	test_config_global rebase.merges true &&
> +	test_config rebase.merges false &&
> +	git checkout -b override-global-config E &&
> +	git rebase C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success 'local rebase.merges="" does not override global rebase.merges=true' '
> +	test_config_global rebase.merges no-rebase-cousins &&
> +	test_config rebase.merges "" &&
> +	git checkout -b no-override-global-config E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase C &&
> +	test_cmp_rev HEAD $before
> +'

These two tests seem to be testing the config subsystem rather than this patch. As Dscho has pointed out it is important to get a balance between test coverage and test run time. I think these two tests can definitely be dropped.

Best Wishes
Phillip
Alex Henrie· Feb 24, 2023, 17:51 UTC · re: Phillip Wood · lore

Re: [PATCH v4 3/3] rebase: add a config option for --rebase-merges

On Fri, Feb 24, 2023 at 7:55 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> Thanks for updating this, it is much clearer what the different settings
> mean now.
Happy to help. Thanks for the feedback.
> I not sure if having the config setting override the default
> when the user passes --rebase-merges without an argument is a good idea.
> I think this behavior is confusing for users and will break scripts that
> quite reasonably assume --rebase-merges is equivalent to
> --rebase-merges=no-rebase-cousins

In that case, we're going to need two config options: A rebase.merges boolean for whether --rebase-merges should be on by default and a rebase.cousins boolean for whether rebase-cousins or no-rebase-cousins is the default.

I will try to incorporate that change and the others that you and Johannes suggested in v5.

-Alex
Junio C Hamano· Feb 23, 2023, 17:28 UTC · re: Alex Henrie · lore

Re: [PATCH v4 1/3] rebase: add documentation and test for --no-rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
No cover letter to summarize the changes?

Comparing this round with the previous, the only two changes are that [1/3] lost one test "do not rebase merges unless asked to", and [2/3] now uses test_must_fail to mark a git command that is expected to stop.

Looks good, but a rerolled patch series should not force reviewers to fetch and compare, but give a summary of changes when it is sent out, to save everybody's time. Preparing a summary before sending it out also helps the patch author, too, that all the intended changes are included in the new round.

Thanks.
Johannes Schindelin· Feb 24, 2023, 13:57 UTC · re: Junio C Hamano · lore

Re: [PATCH v4 1/3] rebase: add documentation and test for --no-rebase-merges

Hi Junio,
On Thu, 23 Feb 2023, Junio C Hamano wrote:
Show 6 quoted lines
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> > Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> > ---
>
> No cover letter to summarize the changes?

A range-diff would have been nice, too, as well as replying-to the previous iteration so that they're all within the same email thread.

And if you want cover letters and range-diffs and correct In-Reply-To headers, I can think of a splendid way to encourage that: promote tools that do that. That's much better than to require contributors to know all the customs and conventions of the project and send mails in the exact desired format...

Ciao, Johannes

Junio C Hamano· Feb 24, 2023, 19:16 UTC · re: Johannes Schindelin · lore

Re: [PATCH v4 1/3] rebase: add documentation and test for --no-rebase-merges

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 11 quoted lines
> On Thu, 23 Feb 2023, Junio C Hamano wrote:
>
>> Alex Henrie <alexhenrie24@gmail.com> writes:
>>
>> > Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
>> > ---
>>
>> No cover letter to summarize the changes?
>
> A range-diff would have been nice, too, as well as replying-to the
> previous iteration so that they're all within the same email thread.

Yes, especially the threading would help both those who missed previous iterations and those who reviewed them. Range-diff is often helpful when came with a good summary. Just like a patch alone without a good proposed log message is not a good way to help reviewers understand the issue the patch tries to solve, a cover with range-diff alone only shows what is different from the previous iteration, but does not say why the changes were made, so it is not a good substitute for an explanation by author's words.

I may have suggested GGG if the author appeared a total beginner with the e-mail workflow (and especially if it were a single-patch topic), but Alex seems to be doing fine otherwise, and also GGG has its own learning curve (e.g. it is rare to see a good cover letter with per-iteration summaries, unless the author is one of those experienced list regulars), so I left it up to Alex to choose how to reach the desired end result.

Thanks.
Alex Henrie· Feb 25, 2023, 18:09 UTC · re: Johannes Schindelin · lore

Re: [PATCH v4 1/3] rebase: add documentation and test for --no-rebase-merges

On Fri, Feb 24, 2023 at 6:58 AM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:

> On Thu, 23 Feb 2023, Junio C Hamano wrote:
>
> > No cover letter to summarize the changes?
Show 8 quoted lines
> A range-diff would have been nice, too, as well as replying-to the
> previous iteration so that they're all within the same email thread.
>
> And if you want cover letters and range-diffs and correct In-Reply-To
> headers, I can think of a splendid way to encourage that: promote tools
> that do that. That's much better than to require contributors to know all
> the customs and conventions of the project and send mails in the exact
> desired format...

It would also help if those expectations were mentioned in Documentation/SubmittingPatches. There's nothing in there about range-diff or In-Reply-To.

I think I followed all of the customs of the Git mailing list for v5. Please let me know if I missed something.

-Alex
Alex Henrie· Feb 25, 2023, 18:03 UTC · re: Alex Henrie · lore

[PATCH v5 0/3] rebase: add a config option for --rebase-merges

Changes from v4:
- deprecate --rebase-merges="" rather than removing it outright
- follow the established convention for what "" and NULL mean as
  booleans
- add tailored error message for conflicting options and tests for it
- rename parse_opt_merges to parse_opt_rebase_merges to avoid confusion
  with parse_opt_merge
- similarly, rename parse_merges_value to parse_rebase_merges_value
- move null check from parse_rebase_merges_value to
  parse_opt_rebase_merges
- remove tests that check whether the config system itself works
Suggestions not incorporated:
- remove the callback function
- make --rebase-merge without an argument override
  rebase.merges=rebase-cousins
- make rebase.merge accept only a subset of the possible boolean values,
  or change the meanings of some of those values
- make --rebase-merge="" and rebase.merge="" do different things without
  warning
- remove tests that verify that the command line option properly
  overrides the config option

Thanks to Johannes, Phillip, and Junio for your help making these patches better. If you feel strongly about one of the unincorporated suggestions, let's continue the discussion and try to figure out how to make it happen.

Alex Henrie (3):
  rebase: add documentation and test for --no-rebase-merges
  rebase: deprecate --rebase-merges=""
  rebase: add a config option for --rebase-merges
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           |  5 +-
 builtin/rebase.c                       | 75 ++++++++++++++++++-------
 t/t3422-rebase-incompatible-options.sh | 12 ++++
 t/t3430-rebase-merges.sh               | 78 ++++++++++++++++++++++++++
 5 files changed, 160 insertions(+), 20 deletions(-)
Range-diff against v4:
1:  e6d44a194c = 1:  76e38ef9f8 rebase: add documentation and test for --no-rebase-merges
2:  393b43c4e1 ! 2:  c6099e6dee rebase: stop accepting --rebase-merges=""
    @@ Metadata
     Author: Alex Henrie <alexhenrie24@gmail.com>
     
      ## Commit message ##
    -    rebase: stop accepting --rebase-merges=""
    +    rebase: deprecate --rebase-merges=""
     
         The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
         empty string argument) has been an undocumented synonym of
    -    --rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid
    +    --rebase-merges=no-rebase-cousins. Deprecate that syntax to avoid
         confusion when a rebase.merges config option is introduced, where
    -    rebase.merges="" will be equivalent to not passing --rebase-merges.
    +    rebase.merges="" will be equivalent to --no-rebase-merges.
     
         Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
     
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
      			 N_("use 'merge-base --fork-point' to refine upstream")),
      		OPT_STRING('s', "strategy", &options.strategy,
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 		imply_merge(&options, "--exec");
      
      	if (rebase_merges) {
    --		if (!*rebase_merges)
    + 		if (!*rebase_merges)
     -			; /* default mode; do nothing */
    --		else if (!strcmp("rebase-cousins", rebase_merges))
    -+		if (!strcmp("rebase-cousins", rebase_merges))
    ++			warning(_("--rebase-merges with an empty string "
    ++				  "argument is deprecated and will stop "
    ++				  "working in a future version of Git. Use "
    ++				  "--rebase-merges=no-rebase-cousins "
    ++				  "instead."));
    + 		else if (!strcmp("rebase-cousins", rebase_merges))
      			options.rebase_cousins = 1;
      		else if (strcmp("no-rebase-cousins", rebase_merges))
    - 			die(_("Unknown mode: %s"), rebase_merges);
     
      ## t/t3430-rebase-merges.sh ##
     @@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless asked for' '
      	EOF
      '
      
    -+test_expect_success '--rebase-merges="" is invalid syntax' '
    -+	echo "fatal: Unknown mode: " >expect &&
    -+	test_must_fail git rebase --rebase-merges="" HEAD^ 2>actual &&
    -+	test_cmp expect actual
    ++test_expect_success '--rebase-merges="" is deprecated' '
    ++	git rebase --rebase-merges="" HEAD^ 2>actual &&
    ++	grep deprecated actual
     +'
     +
      test_expect_success 'refs/rewritten/* is worktree-local' '
3:  b1b6fbfa86 ! 3:  95cba9588c rebase: add a config option for --rebase-merges
    @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
      have `<upstream>` as direct ancestor will keep their original branch point,
     
      ## builtin/rebase.c ##
    +@@ builtin/rebase.c: struct rebase_options {
    + 	int fork_point;
    + 	int update_refs;
    + 	int config_autosquash;
    ++	int config_rebase_merges;
    + 	int config_update_refs;
    + };
    + 
    +@@ builtin/rebase.c: struct rebase_options {
    + 		.allow_empty_message = 1,               \
    + 		.autosquash = -1,                       \
    + 		.config_autosquash = -1,                \
    ++		.rebase_merges = -1,                    \
    ++		.config_rebase_merges = -1,             \
    + 		.update_refs = -1,                      \
    + 		.config_update_refs = -1,               \
    + 	}
     @@ builtin/rebase.c: static int run_specific_rebase(struct rebase_options *opts)
      	return status ? -1 : 0;
      }
      
    -+static void parse_merges_value(struct rebase_options *options, const char *value)
    ++static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
     +{
    -+	if (value) {
    -+		if (!strcmp("no-rebase-cousins", value))
    -+			options->rebase_cousins = 0;
    -+		else if (!strcmp("rebase-cousins", value))
    -+			options->rebase_cousins = 1;
    -+		else
    -+			die(_("Unknown mode: %s"), value);
    -+	}
    -+
    -+	options->rebase_merges = 1;
    ++	if (!strcmp("no-rebase-cousins", value))
    ++		options->rebase_cousins = 0;
    ++	else if (!strcmp("rebase-cousins", value))
    ++		options->rebase_cousins = 1;
    ++	else
    ++		die(_("Unknown rebase-merges mode: %s"), value);
     +}
     +
      static int rebase_config(const char *var, const char *value, void *data)
    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
      		return 0;
      	}
      
    -+	if (!strcmp(var, "rebase.merges") && value && *value) {
    -+		opts->rebase_merges = git_parse_maybe_bool(value);
    -+		if (opts->rebase_merges < 0)
    -+			parse_merges_value(opts, value);
    ++	if (!strcmp(var, "rebase.merges")) {
    ++		opts->config_rebase_merges = git_parse_maybe_bool(value);
    ++		if (opts->config_rebase_merges < 0) {
    ++			opts->config_rebase_merges = 1;
    ++			parse_rebase_merges_value(opts, value);
    ++		}
     +		return 0;
     +	}
     +
    - 	if (!strcmp(var, "rebase.backend")) {
    - 		return git_config_string(&opts->default_backend, var, value);
    - 	}
    + 	if (!strcmp(var, "rebase.updaterefs")) {
    + 		opts->config_update_refs = git_config_bool(var, value);
    + 		return 0;
     @@ builtin/rebase.c: static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
      	return 0;
      }
      
    -+static int parse_opt_merges(const struct option *opt, const char *arg, int unset)
    ++static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
     +{
     +	struct rebase_options *options = opt->value;
     +
    -+	if (unset)
    -+		options->rebase_merges = 0;
    -+	else
    -+		parse_merges_value(options, arg);
    ++	options->rebase_merges = !unset;
    ++
    ++	if (arg) {
    ++		if (!*arg) {
    ++			warning(_("--rebase-merges with an empty string "
    ++				  "argument is deprecated and will stop "
    ++				  "working in a future version of Git. Use "
    ++				  "--rebase-merges=no-rebase-cousins "
    ++				  "instead."));
    ++			arg = "no-rebase-cousins";
    ++		}
    ++		parse_rebase_merges_value(options, arg);
    ++	}
     +
     +	return 0;
     +}
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
     +		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
      			N_("try to rebase merges instead of skipping them"),
     -			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
    -+			PARSE_OPT_OPTARG, parse_opt_merges),
    ++			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
      		OPT_BOOL(0, "fork-point", &options.fork_point,
      			 N_("use 'merge-base --fork-point' to refine upstream")),
      		OPT_STRING('s', "strategy", &options.strategy,
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
      		imply_merge(&options, "--exec");
      
     -	if (rebase_merges) {
    --		if (!strcmp("rebase-cousins", rebase_merges))
    +-		if (!*rebase_merges)
    +-			warning(_("--rebase-merges with an empty string "
    +-				  "argument is deprecated and will stop "
    +-				  "working in a future version of Git. Use "
    +-				  "--rebase-merges=no-rebase-cousins "
    +-				  "instead."));
    +-		else if (!strcmp("rebase-cousins", rebase_merges))
     -			options.rebase_cousins = 1;
     -		else if (strcmp("no-rebase-cousins", rebase_merges))
     -			die(_("Unknown mode: %s"), rebase_merges);
     -		options.rebase_merges = 1;
    -+	if (options.rebase_merges)
    - 		imply_merge(&options, "--rebase-merges");
    +-		imply_merge(&options, "--rebase-merges");
     -	}
    - 
    +-
      	if (options.type == REBASE_APPLY) {
      		if (ignore_whitespace)
    + 			strvec_push(&options.git_am_opts,
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + 				break;
    + 
    + 		if (i >= 0 || options.type == REBASE_APPLY) {
    +-			if (is_merge(&options))
    +-				die(_("apply options and merge options "
    +-					  "cannot be used together"));
    +-			else if (options.autosquash == -1 && options.config_autosquash == 1)
    ++			if (options.autosquash == -1 && options.config_autosquash == 1)
    + 				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
    ++			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
    ++				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
    + 			else if (options.update_refs == -1 && options.config_update_refs == 1)
    + 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
    ++			else if (is_merge(&options))
    ++				die(_("apply options and merge options "
    ++					  "cannot be used together"));
    + 			else
    + 				options.type = REBASE_APPLY;
    + 		}
    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    + 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
    + 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
    + 
    ++	if (options.rebase_merges == 1)
    ++		imply_merge(&options, "--rebase-merges");
    ++	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
    ++				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
    ++
    + 	if (options.autosquash == 1)
    + 		imply_merge(&options, "--autosquash");
    + 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
    +
    + ## t/t3422-rebase-incompatible-options.sh ##
    +@@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
    + 		grep -e --no-autosquash err
    + 	"
    + 
    ++	test_expect_success "$opt incompatible with rebase.merges" "
    ++		git checkout B^0 &&
    ++		test_must_fail git -c rebase.merges=true rebase $opt A 2>err &&
    ++		grep -e --no-rebase-merges err
    ++	"
    ++
    + 	test_expect_success "$opt incompatible with rebase.updateRefs" "
    + 		git checkout B^0 &&
    + 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
    +@@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
    + 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
    + 	"
    + 
    ++	test_expect_success "$opt okay with overridden rebase.merges" "
    ++		test_when_finished \"git reset --hard B^0\" &&
    ++		git checkout B^0 &&
    ++		git -c rebase.merges=true rebase --no-rebase-merges $opt A
    ++	"
    ++
    + 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
    + 		test_when_finished \"git reset --hard B^0\" &&
    + 		git checkout B^0 &&
     
      ## t/t3430-rebase-merges.sh ##
    -@@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is invalid syntax' '
    - 	test_cmp expect actual
    +@@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated' '
    + 	grep deprecated actual
      '
      
    -+test_expect_success 'rebase.merges="" is equivalent to not passing --rebase-merges' '
    -+	test_config rebase.merges "" &&
    -+	git checkout -b config-merges-blank E &&
    -+	git rebase C &&
    -+	test_cmp_graph C.. <<-\EOF
    -+	* B
    -+	* D
    -+	o C
    -+	EOF
    -+'
    -+
     +test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
     +	test_config rebase.merges rebase-cousins &&
     +	git checkout -b config-rebase-cousins main &&
    @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is invalid syn
     +	o H
     +	EOF
     +'
    -+
    -+test_expect_success 'local rebase.merges=false overrides global rebase.merges=true' '
    -+	test_config_global rebase.merges true &&
    -+	test_config rebase.merges false &&
    -+	git checkout -b override-global-config E &&
    -+	git rebase C &&
    -+	test_cmp_graph C.. <<-\EOF
    -+	* B
    -+	* D
    -+	o C
    -+	EOF
    -+'
    -+
    -+test_expect_success 'local rebase.merges="" does not override global rebase.merges=true' '
    -+	test_config_global rebase.merges no-rebase-cousins &&
    -+	test_config rebase.merges "" &&
    -+	git checkout -b no-override-global-config E &&
    -+	before="$(git rev-parse --verify HEAD)" &&
    -+	test_tick &&
    -+	git rebase C &&
    -+	test_cmp_rev HEAD $before
    -+'
     +
      test_expect_success 'refs/rewritten/* is worktree-local' '
      	git worktree add wt &&
-- 
2.39.2
Alex Henrie· Feb 25, 2023, 18:03 UTC · re: Alex Henrie · lore

[PATCH v5 1/3] rebase: add documentation and test for --no-rebase-merges

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt |  4 +++-
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)
Show changes to 2 files +13 −1

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..c98784a0d2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,13 +529,15 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
 By default, or when `no-rebase-cousins` was specified, commits which do not
 have `<upstream>` as direct ancestor will keep their original branch point,
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.39.2
Glen Choo· Mar 1, 2023, 23:23 UTC · re: Alex Henrie · lore

Re: [PATCH v5 1/3] rebase: add documentation and test for --no-rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>

I inferred from the 'subject line' that this "always" worked and was undocumented, and that we'd want to document this not for its own sake, but because we'll introduce a config option. I would personally prefer to get that explicitly in writing, but it's definitely not worth a reroll.

Show 16 quoted lines
>  -r::
>  --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
> +--no-rebase-merges::
>  	By default, a rebase will simply drop merge commits from the todo
>  	list, and put the rebased commits into a single, linear branch.
>  	With `--rebase-merges`, the rebase will instead try to preserve
>  	the branching structure within the commits that are to be rebased,
>  	by recreating the merge commits. Any resolved merge conflicts or
>  	manual amendments in these merge commits will have to be
> -	resolved/re-applied manually.
> +	resolved/re-applied manually. `--no-rebase-merges` can be used to
> +	countermand a previous `--rebase-merges`.
>  +
>  By default, or when `no-rebase-cousins` was specified, commits which do not
>  have `<upstream>` as direct ancestor will keep their original branch point,
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh

This isn't the fault of this patch, but I find this description a little confusing. First we say

  By default (*when no option is given*), a rebase will simply drop
  merge commits from the todo list...
and then later we also say
  By default (*when --rebase-merges is provided without a value), or
  when `no-rebase-cousins`...

So although you didn't create this problem, given the changes we're making in the later patches, I think we have the chance to make this description more sensible; I'll elaborate in my response to those patches.

Alex Henrie· Feb 25, 2023, 18:03 UTC · re: Alex Henrie · lore

[PATCH v5 2/3] rebase: deprecate --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges=no-rebase-cousins. Deprecate that syntax to avoid confusion when a rebase.merges config option is introduced, where rebase.merges="" will be equivalent to --no-rebase-merges.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c         | 8 ++++++--
 t/t3430-rebase-merges.sh | 5 +++++
 2 files changed, 11 insertions(+), 2 deletions(-)
Show changes to 2 files +11 −2

builtin/rebase.c, t/t3430-rebase-merges.sh

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6635f10d52..ccc13200b5 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1438,7 +1438,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (rebase_merges) {
 		if (!*rebase_merges)
-			; /* default mode; do nothing */
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges=no-rebase-cousins "
+				  "instead."));
 		else if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..f50fbf1390 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,11 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success '--rebase-merges="" is deprecated' '
+	git rebase --rebase-merges="" HEAD^ 2>actual &&
+	grep deprecated actual
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Glen Choo· Mar 1, 2023, 23:46 UTC · re: Alex Henrie · lore

Re: [PATCH v5 2/3] rebase: deprecate --rebase-merges=""

Alex Henrie <alexhenrie24@gmail.com> writes:
> The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
> empty string argument) has been an undocumented synonym of
> --rebase-merges=no-rebase-cousins.
[...]
Show 11 quoted lines
>  	if (rebase_merges) {
>  		if (!*rebase_merges)
> -			; /* default mode; do nothing */
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges=no-rebase-cousins "
> +				  "instead."));
>  		else if (!strcmp("rebase-cousins", rebase_merges))
>  			options.rebase_cousins = 1;
>  		else if (strcmp("no-rebase-cousins", rebase_merges))

As I mentioned in my review of patch 3/3, I think we might be better served by saying that --rebase-merges="" is a synonym of --rebase-merges (aka give me a sane default) instead of giving a specific value like "no-rebase-cousins". This would be give us leeway to change the default behavior in the future.

Phillip Wood· Mar 2, 2023, 10:07 UTC · re: Alex Henrie · lore

Re: [PATCH v5 2/3] rebase: deprecate --rebase-merges=""

Hi Alex
On 25/02/2023 18:03, Alex Henrie wrote:
Show 5 quoted lines
> The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
> empty string argument) has been an undocumented synonym of
> --rebase-merges=no-rebase-cousins. Deprecate that syntax to avoid
> confusion when a rebase.merges config option is introduced, where
> rebase.merges="" will be equivalent to --no-rebase-merges.

I think deprecating this rather than making it an error is a good idea. I don't think we need the test though. The test suite is incredibly slow to run on windows (I'd heard people complain about it but it was not until I tried running it myself I realized just how diabolically slow it really is) and so it is important to balance adding tests to catch regression vs test run time. Tests that catch bugs in the rebase implementation are useful, ones like this just make everything slower which makes it harder to catch real regressions.

Best Wishes
Phillip
Show 48 quoted lines
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
>   builtin/rebase.c         | 8 ++++++--
>   t/t3430-rebase-merges.sh | 5 +++++
>   2 files changed, 11 insertions(+), 2 deletions(-)
> 
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 6635f10d52..ccc13200b5 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
>   			N_("mode"),
>   			N_("try to rebase merges instead of skipping them"),
> -			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
> +			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
>   		OPT_BOOL(0, "fork-point", &options.fork_point,
>   			 N_("use 'merge-base --fork-point' to refine upstream")),
>   		OPT_STRING('s', "strategy", &options.strategy,
> @@ -1438,7 +1438,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   
>   	if (rebase_merges) {
>   		if (!*rebase_merges)
> -			; /* default mode; do nothing */
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges=no-rebase-cousins "
> +				  "instead."));
>   		else if (!strcmp("rebase-cousins", rebase_merges))
>   			options.rebase_cousins = 1;
>   		else if (strcmp("no-rebase-cousins", rebase_merges))
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
> index d46d9545f2..f50fbf1390 100755
> --- a/t/t3430-rebase-merges.sh
> +++ b/t/t3430-rebase-merges.sh
> @@ -278,6 +278,11 @@ test_expect_success 'do not rebase cousins unless asked for' '
>   	EOF
>   '
>   
> +test_expect_success '--rebase-merges="" is deprecated' '
> +	git rebase --rebase-merges="" HEAD^ 2>actual &&
> +	grep deprecated actual
> +'
> +
>   test_expect_success 'refs/rewritten/* is worktree-local' '
>   	git worktree add wt &&
>   	cat >wt/script-from-scratch <<-\EOF &&
Calvin Wan· Mar 2, 2023, 18:02 UTC · re: Alex Henrie · lore

Re: [PATCH v5 2/3] rebase: deprecate --rebase-merges=""

Show 6 quoted lines
> -			; /* default mode; do nothing */
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges=no-rebase-cousins "
> +				  "instead."));

Just a small nit on the warning message. It should describe to the user what behavior is happening here and presumably the user wanted `--rebase-merges` with no argument. For example, "Defaulting to --rebase-merges instead."

Alex Henrie· Feb 25, 2023, 18:03 UTC · re: Alex Henrie · lore

[PATCH v5 3/3] 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 possibly turning on --rebase-merges by default without configuration in a future version of Git.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           |  3 +-
 builtin/rebase.c                       | 79 ++++++++++++++++++--------
 t/t3422-rebase-incompatible-options.sh | 12 ++++
 t/t3430-rebase-merges.sh               | 63 ++++++++++++++++++++
 5 files changed, 143 insertions(+), 24 deletions(-)
Show changes to 5 files +143 −24

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3422-rebase-incompatible-options.sh, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..308baa9dbb 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.merges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true is equivalent to `--rebase-merges` without an argument, setting to
+	`rebase-cousins` or `no-rebase-cousins` is equivalent to
+	`--rebase-merges` with that value as its argument, and setting to false
+	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line without an argument overrides a `rebase.merges=false`
+	configuration but does not override other values of `rebase.merge`.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c98784a0d2..b02f9cbb8c 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.merges` config option and a previous
+	`--rebase-merges`.
 +
 By default, or when `no-rebase-cousins` was specified, commits which do not
 have `<upstream>` as direct ancestor will keep their original branch point,
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ccc13200b5..ac096f27e1 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -123,6 +123,7 @@ struct rebase_options {
 	int fork_point;
 	int update_refs;
 	int config_autosquash;
+	int config_rebase_merges;
 	int config_update_refs;
 };
 
@@ -140,6 +141,8 @@ struct rebase_options {
 		.allow_empty_message = 1,               \
 		.autosquash = -1,                       \
 		.config_autosquash = -1,                \
+		.rebase_merges = -1,                    \
+		.config_rebase_merges = -1,             \
 		.update_refs = -1,                      \
 		.config_update_refs = -1,               \
 	}
@@ -771,6 +774,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+	if (!strcmp("no-rebase-cousins", value))
+		options->rebase_cousins = 0;
+	else if (!strcmp("rebase-cousins", value))
+		options->rebase_cousins = 1;
+	else
+		die(_("Unknown rebase-merges mode: %s"), value);
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -800,6 +813,15 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.merges")) {
+		opts->config_rebase_merges = git_parse_maybe_bool(value);
+		if (opts->config_rebase_merges < 0) {
+			opts->config_rebase_merges = 1;
+			parse_rebase_merges_value(opts, value);
+		}
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.updaterefs")) {
 		opts->config_update_refs = git_config_bool(var, value);
 		return 0;
@@ -980,6 +1002,27 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	options->rebase_merges = !unset;
+
+	if (arg) {
+		if (!*arg) {
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges=no-rebase-cousins "
+				  "instead."));
+			arg = "no-rebase-cousins";
+		}
+		parse_rebase_merges_value(options, arg);
+	}
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1035,7 +1078,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1137,10 +1179,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1436,21 +1477,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!*rebase_merges)
-			warning(_("--rebase-merges with an empty string "
-				  "argument is deprecated and will stop "
-				  "working in a future version of Git. Use "
-				  "--rebase-merges=no-rebase-cousins "
-				  "instead."));
-		else if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
-		imply_merge(&options, "--rebase-merges");
-	}
-
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
 			strvec_push(&options.git_am_opts,
@@ -1513,13 +1539,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				break;
 
 		if (i >= 0 || options.type == REBASE_APPLY) {
-			if (is_merge(&options))
-				die(_("apply options and merge options "
-					  "cannot be used together"));
-			else if (options.autosquash == -1 && options.config_autosquash == 1)
+			if (options.autosquash == -1 && options.config_autosquash == 1)
 				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
+			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
 			else if (options.update_refs == -1 && options.config_update_refs == 1)
 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
+			else if (is_merge(&options))
+				die(_("apply options and merge options "
+					  "cannot be used together"));
 			else
 				options.type = REBASE_APPLY;
 		}
@@ -1530,6 +1558,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
+	if (options.rebase_merges == 1)
+		imply_merge(&options, "--rebase-merges");
+	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
 	if (options.autosquash == 1)
 		imply_merge(&options, "--autosquash");
 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index 4711b37a28..af93f13849 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -101,6 +101,12 @@ test_rebase_am_only () {
 		grep -e --no-autosquash err
 	"
 
+	test_expect_success "$opt incompatible with rebase.merges" "
+		git checkout B^0 &&
+		test_must_fail git -c rebase.merges=true rebase $opt A 2>err &&
+		grep -e --no-rebase-merges err
+	"
+
 	test_expect_success "$opt incompatible with rebase.updateRefs" "
 		git checkout B^0 &&
 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +119,12 @@ test_rebase_am_only () {
 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
 	"
 
+	test_expect_success "$opt okay with overridden rebase.merges" "
+		test_when_finished \"git reset --hard B^0\" &&
+		git checkout B^0 &&
+		git -c rebase.merges=true rebase --no-rebase-merges $opt A
+	"
+
 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
 		test_when_finished \"git reset --hard B^0\" &&
 		git checkout B^0 &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index f50fbf1390..a825a77fb4 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -283,6 +283,69 @@ test_expect_success '--rebase-merges="" is deprecated' '
 	grep deprecated actual
 '
 
+test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
+	test_config rebase.merges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b override-config-rebase-cousins main &&
+	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	o | H
+	|/
+	o A
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.merges=false' '
+	test_config rebase.merges false &&
+	git checkout -b override-config-merges-false E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
+test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
+	test_config rebase.merges rebase-cousins &&
+	git checkout -b no-override-config-rebase-cousins main &&
+	git rebase --rebase-merges HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Glen Choo· Mar 1, 2023, 23:43 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
> +rebase.merges::

As far as I can tell, when a config option is meant to be a stand in for a CLI option, we have typically named it <subcommand>.<camelcase option>, e.g. grep.fullName, log.diffMerges, push.followTags. This probably matters more if we aren't specifying the subcommand that this flag is more (like in this patch).

By this convention, the config option would be rebase.rebaseMerges, which _does_ feel redundant and it's unlikely for "git rebase" to learn a "--merges" flag, so I don't have a strong opinion either way. Though as you mentioned in review club, this consistency might make it easier for users to read custom configs that use the "rebase." section.

Show 8 quoted lines
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true is equivalent to `--rebase-merges` without an argument, setting to
> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
> +	`--rebase-merges` with that value as its argument, and setting to false
> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line without an argument overrides a `rebase.merges=false`
> +	configuration but does not override other values of `rebase.merge`.

With the explanation here, I think having "--rebase-merges" _not_ override "rebase.merge=rebase-cousins" is quite confusing.

I think there are two possibilities forward:
- Be very consistent that "--rebase-merges" is just a synonym of
  "--rebase-merges=no-rebase-cousins". Then, have any value of
  "--rebase-merges" override any config value.
  I think this the easiest UX to understand, but I don't think we should
  be commiting to a default, especially since we may add a more sensible
  default (like rebasing 'evil merges') in the future.
- Keep the existing behavior, but reword the docs to indicate that the
  "no value" form means "I want the default but don't care what it is",
  and an explicit value means "I want to use a particular mode and the
  rules of config vs CLI specificity should apply". Perhaps something
  like:
    When rebasing merges, there are two modes: `no-rebase-cousins` and
    `rebase-cousins`. If no mode is specified in either the config or
    CLI, it defaults to `no-rebase-cousins`.
   which I think is clearer that "rebase.merges=some-val" will win out
   over "--rebase-merges". What's nice is that we don't commit to a
   default value, so we can change it without having to update much of
   the wording here.
The code looks good, no real comments there.
Phillip Wood· Mar 2, 2023, 09:37 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 25/02/2023 18:03, Alex Henrie wrote:
Show 32 quoted lines
> The purpose of the new option is to accommodate users who would like
> --rebase-merges to be on by default and to facilitate possibly turning
> on --rebase-merges by default without configuration in a future version
> of Git.
> 
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
>   Documentation/config/rebase.txt        | 10 ++++
>   Documentation/git-rebase.txt           |  3 +-
>   builtin/rebase.c                       | 79 ++++++++++++++++++--------
>   t/t3422-rebase-incompatible-options.sh | 12 ++++
>   t/t3430-rebase-merges.sh               | 63 ++++++++++++++++++++
>   5 files changed, 143 insertions(+), 24 deletions(-)
> 
> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
> index f19bd0e040..308baa9dbb 100644
> --- a/Documentation/config/rebase.txt
> +++ b/Documentation/config/rebase.txt
> @@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
>   
>   rebase.forkPoint::
>   	If set to false set `--no-fork-point` option by default.
> +
> +rebase.merges::
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true is equivalent to `--rebase-merges` without an argument, setting to
> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
> +	`--rebase-merges` with that value as its argument, and setting to false
> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line without an argument overrides a `rebase.merges=false`
> +	configuration but does not override other values of `rebase.merge`.

I'm still not clear why the commandline doesn't override the config in all cases as is our usual practice. After all if the user has set rebase.merges then they don't need to pass --rebase-merges unless they want to override the config.

 > [...]
Show 12 quoted lines
> @@ -1513,13 +1539,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   				break;
>   
>   		if (i >= 0 || options.type == REBASE_APPLY) {
> -			if (is_merge(&options))
> -				die(_("apply options and merge options "
> -					  "cannot be used together"));
> -			else if (options.autosquash == -1 && options.config_autosquash == 1)
> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>   				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
> +				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
This is good, thanks for adding it
 > [...]
Show 47 quoted lines
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
> index f50fbf1390..a825a77fb4 100755
> --- a/t/t3430-rebase-merges.sh
> +++ b/t/t3430-rebase-merges.sh
> @@ -283,6 +283,69 @@ test_expect_success '--rebase-merges="" is deprecated' '
>   	grep deprecated actual
>   '
>   
> +test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b config-rebase-cousins main &&
> +	git rebase HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
> +test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
> +	test_config rebase.merges no-rebase-cousins &&
> +	git checkout -b override-config-no-rebase-cousins E &&
> +	git rebase --no-rebase-merges C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b override-config-rebase-cousins main &&
> +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	o | H
> +	|/
> +	o A
> +	EOF
> +'

I'm not sure this test adds much value, it is hard to see what kind of regression would allow the others to pass but not this one.

Show 7 quoted lines
> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
> +	test_config rebase.merges false &&
> +	git checkout -b override-config-merges-false E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase --rebase-merges C &&
> +	test_cmp_rev HEAD $before

This test passes if the rebase does nothing, maybe pass --force and check the graph?

Best Wishes
Phillip
Show 19 quoted lines
> +'
> +
> +test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b no-override-config-rebase-cousins main &&
> +	git rebase --rebase-merges HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
>   test_expect_success 'refs/rewritten/* is worktree-local' '
>   	git worktree add wt &&
>   	cat >wt/script-from-scratch <<-\EOF &&
Alex Henrie· Mar 4, 2023, 23:24 UTC · re: Phillip Wood · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 2, 2023 at 2:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 25/02/2023 18:03, Alex Henrie wrote:
Show 14 quoted lines
> > +rebase.merges::
> > +     Whether and how to set the `--rebase-merges` option by default. Can
> > +     be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> > +     true is equivalent to `--rebase-merges` without an argument, setting to
> > +     `rebase-cousins` or `no-rebase-cousins` is equivalent to
> > +     `--rebase-merges` with that value as its argument, and setting to false
> > +     is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> > +     command line without an argument overrides a `rebase.merges=false`
> > +     configuration but does not override other values of `rebase.merge`.
>
> I'm still not clear why the commandline doesn't override the config in
> all cases as is our usual practice. After all if the user has set
> rebase.merges then they don't need to pass --rebase-merges unless they
> want to override the config.

Given the current push to turn rebase-merges on by default, it seems likely that rebase-cousins will also be turned on by default at some point after that. There will be a warning about the default changing, and we'll want to let users suppress that warning by setting rebase.rebaseMerges=rebase-cousins. It would then be very confusing if a --rebase-merges from some old alias continued to mean --rebase-merges=no-rebase-cousins when the user expects it to start behaving as though the default has already changed.

I will rephrase the documentation in v6 to make it more clear that the absence of a specific value on the command line does not clobber a specific value set in the configuration, as Glen suggested.

Show 17 quoted lines
> > +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
> > +     test_config rebase.merges rebase-cousins &&
> > +     git checkout -b override-config-rebase-cousins main &&
> > +     git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
> > +     test_cmp_graph HEAD^.. <<-\EOF
> > +     *   Merge the topic branch '\''onebranch'\''
> > +     |\
> > +     | * D
> > +     | * G
> > +     o | H
> > +     |/
> > +     o A
> > +     EOF
> > +'
>
> I'm not sure this test adds much value, it is hard to see what kind of
> regression would allow the others to pass but not this one.

I was worried that I or someone else would forget to explicitly set rebase_cousins to 0 when no-rebase-cousins is given on the command line, assuming that it is already 0 because that is the default. The test makes me feel better, but I am happy to remove it if you still think it's overkill.

Show 10 quoted lines
> > +test_expect_success '--rebase-merges overrides rebase.merges=false' '
> > +     test_config rebase.merges false &&
> > +     git checkout -b override-config-merges-false E &&
> > +     before="$(git rev-parse --verify HEAD)" &&
> > +     test_tick &&
> > +     git rebase --rebase-merges C &&
> > +     test_cmp_rev HEAD $before
>
> This test passes if the rebase does nothing, maybe pass --force and
> check the graph?

The rebase is supposed to do nothing here. Checking that the commit hash is the same is just a quick way to check that the entire graph is the same. What more would be checked by checking the graph instead of the hash?

Thanks for the feedback,
-Alex
Phillip Wood· Mar 7, 2023, 16:23 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 04/03/2023 23:24, Alex Henrie wrote:
Show 22 quoted lines
> On Thu, Mar 2, 2023 at 2:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> 
>> On 25/02/2023 18:03, Alex Henrie wrote:
> 
>>> +rebase.merges::
>>> +     Whether and how to set the `--rebase-merges` option by default. Can
>>> +     be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
>>> +     true is equivalent to `--rebase-merges` without an argument, setting to
>>> +     `rebase-cousins` or `no-rebase-cousins` is equivalent to
>>> +     `--rebase-merges` with that value as its argument, and setting to false
>>> +     is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
>>> +     command line without an argument overrides a `rebase.merges=false`
>>> +     configuration but does not override other values of `rebase.merge`.
>>
>> I'm still not clear why the commandline doesn't override the config in
>> all cases as is our usual practice. After all if the user has set
>> rebase.merges then they don't need to pass --rebase-merges unless they
>> want to override the config.
> 
> Given the current push to turn rebase-merges on by default, it seems
> likely that rebase-cousins will also be turned on by default at some
> point after that.

It is good to try and future proof things but this seems rather hypothetical. I don't really see how the choice of whether --rebase-merges is turned on by default is related to the choice of whether or not to rebase cousins by default. It is worth noting that the default was changed to from rebase-cousins to no-rebase-cousins early in the development of --rebase-merges[1] as it was felt to be less surprising.

Show 6 quoted lines
> There will be a warning about the default changing,
> and we'll want to let users suppress that warning by setting
> rebase.rebaseMerges=rebase-cousins. It would then be very confusing if
> a --rebase-merges from some old alias continued to mean
> --rebase-merges=no-rebase-cousins when the user expects it to start
> behaving as though the default has already changed.

But aren't you breaking those aliases now when rebase.rebaseMerges=rebase-cousins? That's what I'm objecting to. It seems like we're breaking things now to avoid a hypothetical future change breaking them which does not seem like the right trade off to me.

It also does not fit with the way other optional arguments interact with their associated config setting. For example "git branch/checkout/switch --track" and branch.autoSetupMerge. If the optional argument to --track is omitted it defaults to "direct" irrespective of the config.

[1] https://lore.kernel.org/git/nycvar.QRO.7.76.6.1801292251240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz/

> I will rephrase the documentation in v6 to make it more clear that the
> absence of a specific value on the command line does not clobber a
> specific value set in the configuration, as Glen suggested.
The documentation in v6 is certainly quite clear on this point.
Show 23 quoted lines
>>> +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
>>> +     test_config rebase.merges rebase-cousins &&
>>> +     git checkout -b override-config-rebase-cousins main &&
>>> +     git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
>>> +     test_cmp_graph HEAD^.. <<-\EOF
>>> +     *   Merge the topic branch '\''onebranch'\''
>>> +     |\
>>> +     | * D
>>> +     | * G
>>> +     o | H
>>> +     |/
>>> +     o A
>>> +     EOF
>>> +'
>>
>> I'm not sure this test adds much value, it is hard to see what kind of
>> regression would allow the others to pass but not this one.
> 
> I was worried that I or someone else would forget to explicitly set
> rebase_cousins to 0 when no-rebase-cousins is given on the command
> line, assuming that it is already 0 because that is the default. The
> test makes me feel better, but I am happy to remove it if you still
> think it's overkill.

Given we're using the same code to parse the command line argument and the config setting and we have a test for rebase.rebaseMerges=no-rebase-cousins I think we could drop it.

Show 12 quoted lines
>>> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
>>> +     test_config rebase.merges false &&
>>> +     git checkout -b override-config-merges-false E &&
>>> +     before="$(git rev-parse --verify HEAD)" &&
>>> +     test_tick &&
>>> +     git rebase --rebase-merges C &&
>>> +     test_cmp_rev HEAD $before
>>
>> This test passes if the rebase does nothing, maybe pass --force and
>> check the graph?
> 
> The rebase is supposed to do nothing here.

It's not doing nothing though - it is rebasing the branch, it just happens that everything fast-forwards so HEAD ends up unchanged. My point is that this test should verify the branch has been rebased. Maybe you could check the reflog message for HEAD@{0} is

	rebase (finish): returning to refs/heads/override-config-merges-false
> Checking that the commit
> hash is the same is just a quick way to check that the entire graph is
> the same. What more would be checked by checking the graph instead of
> the hash?

By using --force and checking the graph you check that the rebase actually happened.

Thanks for working on this
Phillip
> Thanks for the feedback,
> 
> -Alex
Alex Henrie· Mar 12, 2023, 20:57 UTC · re: Phillip Wood · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

On Tue, Mar 7, 2023 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 30 quoted lines
> On 04/03/2023 23:24, Alex Henrie wrote:
> > On Thu, Mar 2, 2023 at 2:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> >
> >> On 25/02/2023 18:03, Alex Henrie wrote:
> >
> >>> +rebase.merges::
> >>> +     Whether and how to set the `--rebase-merges` option by default. Can
> >>> +     be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> >>> +     true is equivalent to `--rebase-merges` without an argument, setting to
> >>> +     `rebase-cousins` or `no-rebase-cousins` is equivalent to
> >>> +     `--rebase-merges` with that value as its argument, and setting to false
> >>> +     is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> >>> +     command line without an argument overrides a `rebase.merges=false`
> >>> +     configuration but does not override other values of `rebase.merge`.
> >>
> >> I'm still not clear why the commandline doesn't override the config in
> >> all cases as is our usual practice. After all if the user has set
> >> rebase.merges then they don't need to pass --rebase-merges unless they
> >> want to override the config.
> >
> > Given the current push to turn rebase-merges on by default, it seems
> > likely that rebase-cousins will also be turned on by default at some
> > point after that.
>
> It is good to try and future proof things but this seems rather
> hypothetical. I don't really see how the choice of whether
> --rebase-merges is turned on by default is related to the choice of
> whether or not to rebase cousins by default. It is worth noting that the
> default was changed to from rebase-cousins to no-rebase-cousins early in
> the development of --rebase-merges[1] as it was felt to be less surprising.
> [1]
> https://lore.kernel.org/git/nycvar.QRO.7.76.6.1801292251240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz/

Thank you for sharing that link. Even though I got the tests right, I got confused and started thinking that rebase-cousins was a more thorough version of rebase-merges. In fact, they do opposite things: rebase-merges tries to preserve the graph and rebase-cousins intentionally restructures the graph. In my opinion using the word "rebase" in the names of both options was another unfortunate UI decision, but I understand the difference now.

Show 16 quoted lines
> > There will be a warning about the default changing,
> > and we'll want to let users suppress that warning by setting
> > rebase.rebaseMerges=rebase-cousins. It would then be very confusing if
> > a --rebase-merges from some old alias continued to mean
> > --rebase-merges=no-rebase-cousins when the user expects it to start
> > behaving as though the default has already changed.
>
> But aren't you breaking those aliases now when
> rebase.rebaseMerges=rebase-cousins? That's what I'm objecting to. It
> seems like we're breaking things now to avoid a hypothetical future
> change breaking them which does not seem like the right trade off to me.
>
> It also does not fit with the way other optional arguments interact with
> their associated config setting. For example "git branch/checkout/switch
> --track" and branch.autoSetupMerge. If the optional argument to --track
> is omitted it defaults to "direct" irrespective of the config.

What I really don't want is to paint ourselves into a corner. You're right that it's unlikely that the default will ever change from no-rebase-cousins to rebase-cousins; I was mistaken. However, Glen thinks that in the future we might have some kind of rebase-evil-merges mode as well, and that that might become the default. If we don't let the rebase.rebaseMerges config value control the default behavior of --rebase-merges without an argument on the command line, we would have to introduce a separate config option for the transition, which would be ugly.

More voices would be helpful here. Does anyone else have an opinion on how likely it is that the default rebase-merges mode will change in the future? Or on whether rebase.rebaseMerges should be allowed to affect --rebase-merges in order to facilitate such a change?

Show 27 quoted lines
> >>> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
> >>> +     test_config rebase.merges false &&
> >>> +     git checkout -b override-config-merges-false E &&
> >>> +     before="$(git rev-parse --verify HEAD)" &&
> >>> +     test_tick &&
> >>> +     git rebase --rebase-merges C &&
> >>> +     test_cmp_rev HEAD $before
> >>
> >> This test passes if the rebase does nothing, maybe pass --force and
> >> check the graph?
> >
> > The rebase is supposed to do nothing here.
>
> It's not doing nothing though - it is rebasing the branch, it just
> happens that everything fast-forwards so HEAD ends up unchanged. My
> point is that this test should verify the branch has been rebased. Maybe
> you could check the reflog message for HEAD@{0} is
>
>         rebase (finish): returning to refs/heads/override-config-merges-false
>
> > Checking that the commit
> > hash is the same is just a quick way to check that the entire graph is
> > the same. What more would be checked by checking the graph instead of
> > the hash?
>
> By using --force and checking the graph you check that the rebase
> actually happened.

I got the impression that people like that not checking the graph itself (or the reflog) makes the tests more concise, but I don't care much either way. For what it's worth, the way I did it matches the existing tests in the file. If you can find at least one other person who thinks that it ought to change for this patch series to be accepted, and no one else objects, I'll change it.

> Thanks for working on this
You're welcome; hopefully we can get the remaining details ironed out quickly.
-Alex
Phillip Wood· Mar 13, 2023, 14:20 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 12/03/2023 20:57, Alex Henrie wrote:
Show 60 quoted lines
> On Tue, Mar 7, 2023 at 9:23 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> 
>> On 04/03/2023 23:24, Alex Henrie wrote:
>>> On Thu, Mar 2, 2023 at 2:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>>
>>>> On 25/02/2023 18:03, Alex Henrie wrote:
>>>
>>>>> +rebase.merges::
>>>>> +     Whether and how to set the `--rebase-merges` option by default. Can
>>>>> +     be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
>>>>> +     true is equivalent to `--rebase-merges` without an argument, setting to
>>>>> +     `rebase-cousins` or `no-rebase-cousins` is equivalent to
>>>>> +     `--rebase-merges` with that value as its argument, and setting to false
>>>>> +     is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
>>>>> +     command line without an argument overrides a `rebase.merges=false`
>>>>> +     configuration but does not override other values of `rebase.merge`.
>>>>
>>>> I'm still not clear why the commandline doesn't override the config in
>>>> all cases as is our usual practice. After all if the user has set
>>>> rebase.merges then they don't need to pass --rebase-merges unless they
>>>> want to override the config.
>>>
>>> Given the current push to turn rebase-merges on by default, it seems
>>> likely that rebase-cousins will also be turned on by default at some
>>> point after that.
>>
>> It is good to try and future proof things but this seems rather
>> hypothetical. I don't really see how the choice of whether
>> --rebase-merges is turned on by default is related to the choice of
>> whether or not to rebase cousins by default. It is worth noting that the
>> default was changed to from rebase-cousins to no-rebase-cousins early in
>> the development of --rebase-merges[1] as it was felt to be less surprising.
> 
>> [1]
>> https://lore.kernel.org/git/nycvar.QRO.7.76.6.1801292251240.35@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz/
> 
> Thank you for sharing that link. Even though I got the tests right, I
> got confused and started thinking that rebase-cousins was a more
> thorough version of rebase-merges. In fact, they do opposite things:
> rebase-merges tries to preserve the graph and rebase-cousins
> intentionally restructures the graph. In my opinion using the word
> "rebase" in the names of both options was another unfortunate UI
> decision, but I understand the difference now.
> 
>>> There will be a warning about the default changing,
>>> and we'll want to let users suppress that warning by setting
>>> rebase.rebaseMerges=rebase-cousins. It would then be very confusing if
>>> a --rebase-merges from some old alias continued to mean
>>> --rebase-merges=no-rebase-cousins when the user expects it to start
>>> behaving as though the default has already changed.
>>
>> But aren't you breaking those aliases now when
>> rebase.rebaseMerges=rebase-cousins? That's what I'm objecting to. It
>> seems like we're breaking things now to avoid a hypothetical future
>> change breaking them which does not seem like the right trade off to me.
>>
>> It also does not fit with the way other optional arguments interact with
>> their associated config setting. For example "git branch/checkout/switch
>> --track" and branch.autoSetupMerge. If the optional argument to --track
>> is omitted it defaults to "direct" irrespective of the config.
 >
> What I really don't want is to paint ourselves into a corner.

Nor do I but we already seem to be in some kind of corner as what you're proposing breaks the status quo and the existing convention that command line options override config settings.

Show 9 quoted lines
> You're
> right that it's unlikely that the default will ever change from
> no-rebase-cousins to rebase-cousins; I was mistaken. However, Glen
> thinks that in the future we might have some kind of
> rebase-evil-merges mode as well, and that that might become the
> default. If we don't let the rebase.rebaseMerges config value control
> the default behavior of --rebase-merges without an argument on the
> command line, we would have to introduce a separate config option for
> the transition, which would be ugly.

I'm optimistic that Elijah's work on rebasing evil merges will allow us to improve --rebase-merges. I expect that we'll enable it by default once it is merged. I do not think that we should add another argument for --rebase-merges to disable it though as that would be orthogonal to "rebase-cousins" and "no-rebase-cousins" which control what commits get rebased not how merges are rebased. If users want to disable the better rebasing of merges we'll need to add a --remerge option or something like that.

> More voices would be helpful here. Does anyone else have an opinion on
> how likely it is that the default rebase-merges mode will change in
> the future? Or on whether rebase.rebaseMerges should be allowed to
> affect --rebase-merges in order to facilitate such a change?
Getting other's views would indeed be helpful
Best Wishes
Phillip
Show 40 quoted lines
>>>>> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
>>>>> +     test_config rebase.merges false &&
>>>>> +     git checkout -b override-config-merges-false E &&
>>>>> +     before="$(git rev-parse --verify HEAD)" &&
>>>>> +     test_tick &&
>>>>> +     git rebase --rebase-merges C &&
>>>>> +     test_cmp_rev HEAD $before
>>>>
>>>> This test passes if the rebase does nothing, maybe pass --force and
>>>> check the graph?
>>>
>>> The rebase is supposed to do nothing here.
>>
>> It's not doing nothing though - it is rebasing the branch, it just
>> happens that everything fast-forwards so HEAD ends up unchanged. My
>> point is that this test should verify the branch has been rebased. Maybe
>> you could check the reflog message for HEAD@{0} is
>>
>>          rebase (finish): returning to refs/heads/override-config-merges-false
>>
>>> Checking that the commit
>>> hash is the same is just a quick way to check that the entire graph is
>>> the same. What more would be checked by checking the graph instead of
>>> the hash?
>>
>> By using --force and checking the graph you check that the rebase
>> actually happened.
> 
> I got the impression that people like that not checking the graph
> itself (or the reflog) makes the tests more concise, but I don't care
> much either way. For what it's worth, the way I did it matches the
> existing tests in the file. If you can find at least one other person
> who thinks that it ought to change for this patch series to be
> accepted, and no one else objects, I'll change it.
> 
>> Thanks for working on this
> 
> You're welcome; hopefully we can get the remaining details ironed out quickly.
> 
> -Alex
Felipe Contreras· Mar 13, 2023, 16:12 UTC · re: Phillip Wood · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

On Mon, Mar 13, 2023 at 9:38 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 12/03/2023 20:57, Alex Henrie wrote:
Show 6 quoted lines
> > More voices would be helpful here. Does anyone else have an opinion on
> > how likely it is that the default rebase-merges mode will change in
> > the future? Or on whether rebase.rebaseMerges should be allowed to
> > affect --rebase-merges in order to facilitate such a change?
>
> Getting other's views would indeed be helpful

My view is that we shouldn't make assumptions about what is going to happen in the future. Most of the UI design warts of Git come precisely from the fact that somebody in the past did not think something in the future would happen, and when it does the excuse to not do what's sensible in the UI is "it's too late now".

I would vote for whatever is more flexible to whatever happens in the future, not something that seems to make sense now, but adds yet more inertia and later on becomes yet another wart in the list of "oh, if only we had considered that back in 2023".

Cheers.
-- 
Felipe Contreras
Junio C Hamano· Mar 13, 2023, 19:46 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 14 quoted lines
> What I really don't want is to paint ourselves into a corner. You're
> right that it's unlikely that the default will ever change from
> no-rebase-cousins to rebase-cousins; I was mistaken. However, Glen
> thinks that in the future we might have some kind of
> rebase-evil-merges mode as well, and that that might become the
> default. If we don't let the rebase.rebaseMerges config value control
> the default behavior of --rebase-merges without an argument on the
> command line, we would have to introduce a separate config option for
> the transition, which would be ugly.
>
> More voices would be helpful here. Does anyone else have an opinion on
> how likely it is that the default rebase-merges mode will change in
> the future? Or on whether rebase.rebaseMerges should be allowed to
> affect --rebase-merges in order to facilitate such a change?

Any such "opinion" about the future, or the belief that we can somehow predict one, would be wrong anyway. So I am not sure soliciting more voices to look into crystal balls would help all that much. Having said that...

The default rebase-merges behaviour may be improved, but changing it in a totally backward incompatible way _without_ a carefully prepared transition strategy will be very unlikely, simply because existing end users would not allow us to. I do not offhand know if the configuration variable(s) you propose would serve as a good mechanism to help such transition.

Thanks.
Johannes Schindelin· Mar 24, 2023, 14:47 UTC · re: Alex Henrie · lore

About replaying "evil" merges... Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Hi Alex,

Heads up: This mail is totally a tangent and is _not_ intended to distract from the patch series.

On Sun, 12 Mar 2023, Alex Henrie wrote:
> [...] Glen thinks that in the future we might have some kind of
> rebase-evil-merges mode as well, and that that might become the default.

While it probably won't be the default, like, ever, I can well imagine that there would be a good opportunity to add a config setting to make it the default _for that user_.

About implementing that mode... I recently picked up a repair item from my ever-growing TODO list to brush up that "replay merge conflict resolutions and amendments" idea of Phillip's that I had implemented already four years ago (only to find out that it unfortunately caused a lot of nested merge conflicts when using it on Git for Windows' branch thicket; I tried my best to add a test case demonstrating the problematic behavior).

While I had to stop working on it again, I at least left a rebased version at https://github.com/gitgitgadget/git/pull/1491 (in case anybody is interested where I wanted to take this PR, I left comments describing the changes I had planned on looking into).

I mention this PR here in this thread not so much to derail the review, or even to distract from this patch series (I really detest when that happens in reviews of my own patches, so please accept my apology in advance should that happen here, it is _really_ not my intention), but as a point in favor of Glen's suspicion that we might get such a mode in the future to replay evil merges or merges with resolved conflicts.

Ciao, Johannes

Calvin Wan· Mar 2, 2023, 18:02 UTC · re: Alex Henrie · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

Show 10 quoted lines
> +
> +rebase.merges::
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true is equivalent to `--rebase-merges` without an argument, setting to
> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
> +	`--rebase-merges` with that value as its argument, and setting to false
> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line without an argument overrides a `rebase.merges=false`
> +	configuration but does not override other values of `rebase.merge`.

I originally believed that having the boolean values as an option here created unnecessary complexity but I was convinced during Review Club that the tradeoff for a user who just wants to turn this on is fine. I'm just wondering whether this option will possibly have additional values in the future. If so, what would a user expect from setting this config to true?

Small typo at the end s/rebase.merge/rebase.merges
Show 185 quoted lines
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index c98784a0d2..b02f9cbb8c 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
>  	by recreating the merge commits. Any resolved merge conflicts or
>  	manual amendments in these merge commits will have to be
>  	resolved/re-applied manually. `--no-rebase-merges` can be used to
> -	countermand a previous `--rebase-merges`.
> +	countermand both the `rebase.merges` config option and a previous
> +	`--rebase-merges`.
>  +
>  By default, or when `no-rebase-cousins` was specified, commits which do not
>  have `<upstream>` as direct ancestor will keep their original branch point,
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index ccc13200b5..ac096f27e1 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -123,6 +123,7 @@ struct rebase_options {
>  	int fork_point;
>  	int update_refs;
>  	int config_autosquash;
> +	int config_rebase_merges;
>  	int config_update_refs;
>  };
>  
> @@ -140,6 +141,8 @@ struct rebase_options {
>  		.allow_empty_message = 1,               \
>  		.autosquash = -1,                       \
>  		.config_autosquash = -1,                \
> +		.rebase_merges = -1,                    \
> +		.config_rebase_merges = -1,             \
>  		.update_refs = -1,                      \
>  		.config_update_refs = -1,               \
>  	}
> @@ -771,6 +774,16 @@ static int run_specific_rebase(struct rebase_options *opts)
>  	return status ? -1 : 0;
>  }
>  
> +static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
> +{
> +	if (!strcmp("no-rebase-cousins", value))
> +		options->rebase_cousins = 0;
> +	else if (!strcmp("rebase-cousins", value))
> +		options->rebase_cousins = 1;
> +	else
> +		die(_("Unknown rebase-merges mode: %s"), value);
> +}
> +
>  static int rebase_config(const char *var, const char *value, void *data)
>  {
>  	struct rebase_options *opts = data;
> @@ -800,6 +813,15 @@ static int rebase_config(const char *var, const char *value, void *data)
>  		return 0;
>  	}
>  
> +	if (!strcmp(var, "rebase.merges")) {
> +		opts->config_rebase_merges = git_parse_maybe_bool(value);
> +		if (opts->config_rebase_merges < 0) {
> +			opts->config_rebase_merges = 1;
> +			parse_rebase_merges_value(opts, value);
> +		}
> +		return 0;
> +	}
> +
>  	if (!strcmp(var, "rebase.updaterefs")) {
>  		opts->config_update_refs = git_config_bool(var, value);
>  		return 0;
> @@ -980,6 +1002,27 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>  	return 0;
>  }
>  
> +static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +
> +	options->rebase_merges = !unset;
> +
> +	if (arg) {
> +		if (!*arg) {
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges=no-rebase-cousins "
> +				  "instead."));
> +			arg = "no-rebase-cousins";
> +		}
> +		parse_rebase_merges_value(options, arg);
> +	}
> +
> +	return 0;
> +}
> +
>  static void NORETURN error_on_missing_default_upstream(void)
>  {
>  	struct branch *current_branch = branch_get(NULL);
> @@ -1035,7 +1078,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	struct object_id branch_base;
>  	int ignore_whitespace = 0;
>  	const char *gpg_sign = NULL;
> -	const char *rebase_merges = NULL;
>  	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
>  	struct object_id squash_onto;
>  	char *squash_onto_name = NULL;
> @@ -1137,10 +1179,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  			   &options.allow_empty_message,
>  			   N_("allow rebasing commits with empty messages"),
>  			   PARSE_OPT_HIDDEN),
> -		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
> -			N_("mode"),
> +		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
>  			N_("try to rebase merges instead of skipping them"),
> -			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
> +			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
>  		OPT_BOOL(0, "fork-point", &options.fork_point,
>  			 N_("use 'merge-base --fork-point' to refine upstream")),
>  		OPT_STRING('s', "strategy", &options.strategy,
> @@ -1436,21 +1477,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	if (options.exec.nr)
>  		imply_merge(&options, "--exec");
>  
> -	if (rebase_merges) {
> -		if (!*rebase_merges)
> -			warning(_("--rebase-merges with an empty string "
> -				  "argument is deprecated and will stop "
> -				  "working in a future version of Git. Use "
> -				  "--rebase-merges=no-rebase-cousins "
> -				  "instead."));
> -		else if (!strcmp("rebase-cousins", rebase_merges))
> -			options.rebase_cousins = 1;
> -		else if (strcmp("no-rebase-cousins", rebase_merges))
> -			die(_("Unknown mode: %s"), rebase_merges);
> -		options.rebase_merges = 1;
> -		imply_merge(&options, "--rebase-merges");
> -	}
> -
>  	if (options.type == REBASE_APPLY) {
>  		if (ignore_whitespace)
>  			strvec_push(&options.git_am_opts,
> @@ -1513,13 +1539,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  				break;
>  
>  		if (i >= 0 || options.type == REBASE_APPLY) {
> -			if (is_merge(&options))
> -				die(_("apply options and merge options "
> -					  "cannot be used together"));
> -			else if (options.autosquash == -1 && options.config_autosquash == 1)
> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>  				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
> +				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
>  			else if (options.update_refs == -1 && options.config_update_refs == 1)
>  				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
> +			else if (is_merge(&options))
> +				die(_("apply options and merge options "
> +					  "cannot be used together"));
>  			else
>  				options.type = REBASE_APPLY;
>  		}
> @@ -1530,6 +1558,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
>  			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
>  
> +	if (options.rebase_merges == 1)
> +		imply_merge(&options, "--rebase-merges");
> +	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
> +				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
> +
>  	if (options.autosquash == 1)
>  		imply_merge(&options, "--autosquash");
>  	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
> diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
> index 4711b37a28..af93f13849 100755
> --- a/t/t3422-rebase-incompatible-options.sh
> +++ b/t/t3422-rebase-incompatible-options.sh
> @@ -101,6 +101,12 @@ test_rebase_am_only () {
>  		grep -e --no-autosquash err
>  	"
>  
> +	test_expect_success "$opt incompatible with rebase.merges" "
> +		git checkout B^0 &&
> +		test_must_fail git -c rebase.merges=true rebase $opt A 2>err &&
> +		grep -e --no-rebase-merges err
> +	"
> +

Missing an additional test here "$opt incompatible with --rebase-merges" to match the test patterns from the other option/config combos.

Show 95 quoted lines
>  	test_expect_success "$opt incompatible with rebase.updateRefs" "
>  		git checkout B^0 &&
>  		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
> @@ -113,6 +119,12 @@ test_rebase_am_only () {
>  		git -c rebase.autosquash=true rebase --no-autosquash $opt A
>  	"
>  
> +	test_expect_success "$opt okay with overridden rebase.merges" "
> +		test_when_finished \"git reset --hard B^0\" &&
> +		git checkout B^0 &&
> +		git -c rebase.merges=true rebase --no-rebase-merges $opt A
> +	"
> +
>  	test_expect_success "$opt okay with overridden rebase.updateRefs" "
>  		test_when_finished \"git reset --hard B^0\" &&
>  		git checkout B^0 &&
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
> index f50fbf1390..a825a77fb4 100755
> --- a/t/t3430-rebase-merges.sh
> +++ b/t/t3430-rebase-merges.sh
> @@ -283,6 +283,69 @@ test_expect_success '--rebase-merges="" is deprecated' '
>  	grep deprecated actual
>  '
>  
> +test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b config-rebase-cousins main &&
> +	git rebase HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
> +test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
> +	test_config rebase.merges no-rebase-cousins &&
> +	git checkout -b override-config-no-rebase-cousins E &&
> +	git rebase --no-rebase-merges C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b override-config-rebase-cousins main &&
> +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	o | H
> +	|/
> +	o A
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges overrides rebase.merges=false' '
> +	test_config rebase.merges false &&
> +	git checkout -b override-config-merges-false E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase --rebase-merges C &&
> +	test_cmp_rev HEAD $before
> +'
> +
> +test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
> +	test_config rebase.merges rebase-cousins &&
> +	git checkout -b no-override-config-rebase-cousins main &&
> +	git rebase --rebase-merges HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
>  test_expect_success 'refs/rewritten/* is worktree-local' '
>  	git worktree add wt &&
>  	cat >wt/script-from-scratch <<-\EOF &&
> -- 
> 2.39.2
> 
> 
> 
Alex Henrie· Mar 4, 2023, 23:24 UTC · re: Calvin Wan · lore

Re: [PATCH v5 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 2, 2023 at 11:02 AM Calvin Wan <calvinwan@google.com> wrote:
Show 16 quoted lines
> > +rebase.merges::
> > +     Whether and how to set the `--rebase-merges` option by default. Can
> > +     be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> > +     true is equivalent to `--rebase-merges` without an argument, setting to
> > +     `rebase-cousins` or `no-rebase-cousins` is equivalent to
> > +     `--rebase-merges` with that value as its argument, and setting to false
> > +     is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> > +     command line without an argument overrides a `rebase.merges=false`
> > +     configuration but does not override other values of `rebase.merge`.
>
> I originally believed that having the boolean values as an option here
> created unnecessary complexity but I was convinced during Review Club
> that the tradeoff for a user who just wants to turn this on is fine. I'm
> just wondering whether this option will possibly have additional values
> in the future. If so, what would a user expect from setting this config
> to true?

"true" will always mean "rebase merges in the default way, I don't care what it is". During a time period when the default is transitioning, "true" means that the user will get a warning, but there's nothing wrong with continuing to use "true" if they really don't care.

-Alex
Glen Choo· Mar 1, 2023, 23:14 UTC · re: Alex Henrie · lore

Re: [PATCH v5 0/3] rebase: add a config option for --rebase-merges

Hi Alex! It was great to see you at Review Club today. If you'd like the notes, they are available at

https://docs.google.com/document/d/14L8BAumGTpsXpjDY8VzZ4rRtpAjuGrFSRqn3stCuS_w

though reviewers will send their feedback to the mailing list anyway, so don't feel compelled to address feedback that isn't on the mailing list :)

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 8 quoted lines
> Changes from v4:
>
> [...]
>
> Suggestions not incorporated:
>
> [...]
>

I would have appreciated a sentence or two explaining the high level changes in this series, since that would save reviewers from having to read the whole series to get an idea of the overall intent. For this series, I think the subject line captures the intent pretty well, though even simple descriptions like "Add a config option. Also clean up some problems with the --rebase-merges flag." can be useful framing for reviewers.

I appreciate the summary of changes [not] incorporated, I found it useful for getting an idea of what was originally discussed.

On the whole, I think the config option will be quite welcome, and the series is mostly mergeable, but might benefit from some documentation tweaking.

Alex Henrie· Mar 2, 2023, 05:02 UTC · re: Glen Choo · lore

Re: [PATCH v5 0/3] rebase: add a config option for --rebase-merges

On Wed, Mar 1, 2023 at 4:14 PM Glen Choo <chooglen@google.com> wrote:
Show 9 quoted lines
>
> Hi Alex! It was great to see you at Review Club today. If you'd like the
> notes, they are available at
>
> https://docs.google.com/document/d/14L8BAumGTpsXpjDY8VzZ4rRtpAjuGrFSRqn3stCuS_w
>
> though reviewers will send their feedback to the mailing list anyway, so
> don't feel compelled to address feedback that isn't on the mailing list
> :)
Hi Glenn,

Thanks for hosting the call today. I was impressed by the commitment to having discussion and making decisions on the public mailing list, "as though the call never happened". It was a pleasant change from other open source projects I have contributed to where decisions are sometimes made behind closed doors with little to no opportunity to ask questions. Thanks again for making me think a little harder about my patches; I expect to send a v6 by the end of the week.

-Alex
Alex Henrie· Mar 2, 2023, 05:09 UTC · re: Alex Henrie · lore

Re: [PATCH v5 0/3] rebase: add a config option for --rebase-merges

On Wed, Mar 1, 2023 at 10:02 PM Alex Henrie <alexhenrie24@gmail.com> wrote:
>
> Hi Glenn,
Typo: Glen. No matter how much I proofread, I seem to always notice
typos right after I send the email...
Alex Henrie· Mar 5, 2023, 05:07 UTC · re: Alex Henrie · lore

[PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and documents the behavior of the --rebase-merges command line option to avoid confusion about how the config option and the command line option interact.

Changes from v5:
- Add commit message note about --no-rebase-merges having always worked
- Add commit message note about the test for --no-rebase-merges being
  somewhat contrived
- Rephrase the documentation to avoid using the phrase "By default" with
  two different meanings, and in so doing clarify that --rebase-merges
  without an argument is not the same as --no-rebase-merges and not
  passing --rebase-merges is not the same as passing
  --rebase-merges=no-rebase-cousins
- Add commit message note about keeping --rebase-merges="" for now out
  of an abundance of caution
- Rephrase the warning about --rebase-merges="" to recommend
  --rebase-merges without an argument instead, and clarify that that
  will do the same thing
- Remove the test for --rebase-merges=""
- Rename the config option from rebase.merges to rebase.rebaseMerges and
  explain why in the commit message
- Add commit message note about why "true" is a valid option for
  rebase.rebaseMerges and why --rebase-merges without an argument does
  not clobber the mode specified in rebase.rebaseMerges
- Rephrase the documentation to clarify that --rebase-merges without an
  argument does not clobber the mode specified in rebase.rebaseMerges
- Add another test for incompatible options
Suggestions on v5 not incorporated in v6:
- Make --rebase-merges without an argument clobber the mode specified in
  rebase.rebaseMerges
- Remove the test for --rebase-merges=no-rebase-cousins overriding
  rebase.rebaseMerges=rebase-cousins
- In the tests, check the graph itself instead of checking that the
  graph has not changed by checking that the commit hash has not changed

Thanks to Glen, Calvin, Junio, Jonathan, Elijah, and Phillip for your feedback on v5. As before, if you feel strongly about one of the suggestions that I have not incorporated into v6, or if you see something else amiss, let's continue the discussion.

Alex Henrie (3):
  rebase: add documentation and test for --no-rebase-merges
  rebase: deprecate --rebase-merges=""
  rebase: add a config option for --rebase-merges
 Documentation/config/rebase.txt        | 11 ++++
 Documentation/git-rebase.txt           | 19 ++++---
 builtin/rebase.c                       | 75 ++++++++++++++++++-------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 78 ++++++++++++++++++++++++++
 5 files changed, 174 insertions(+), 26 deletions(-)
Range-diff against v5:
1:  76e38ef9f8 ! 1:  bf08c03ba7 rebase: add documentation and test for --no-rebase-merges
    @@ Metadata
      ## Commit message ##
         rebase: add documentation and test for --no-rebase-merges
     
    +    As far as I can tell, --no-rebase-merges has always worked, but has
    +    never been documented. It is especially important to document it before
    +    a rebase.rebaseMerges option is introduced so that users know how to
    +    override the config option on the command line. It's also important to
    +    clarify that --rebase-merges without an argument is not the same as
    +    --no-rebase-merges and not passing --rebase-merges is not the same as
    +    passing --rebase-merges=no-rebase-cousins.
    +
    +    A test case is necessary to make sure that --no-rebase-merges keeps
    +    working after its code is refactored in the following patches of this
    +    series. The test case is a little contrived: It's unlikely that a user
    +    would type both --rebase-merges and --no-rebase-merges at the same time.
    +    However, if an alias is defined which includes --rebase-merges, the user
    +    might decide to add --no-rebase-merges to countermand that part of the
    +    alias but leave alone other flags set by the alias.
    +
         Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
     
      ## Documentation/git-rebase.txt ##
    @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
     +	resolved/re-applied manually. `--no-rebase-merges` can be used to
     +	countermand a previous `--rebase-merges`.
      +
    - By default, or when `no-rebase-cousins` was specified, commits which do not
    - have `<upstream>` as direct ancestor will keep their original branch point,
    +-By default, or when `no-rebase-cousins` was specified, commits which do not
    +-have `<upstream>` as direct ancestor will keep their original branch point,
    +-i.e. commits that would be excluded by linkgit:git-log[1]'s
    +-`--ancestry-path` option will keep their original ancestry by default. If
    +-the `rebase-cousins` mode is turned on, such commits are instead rebased
    +-onto `<upstream>` (or `<onto>`, if specified).
    ++When rebasing merges, there are two modes: `rebase-cousins` and
    ++`no-rebase-cousins`. If the mode is not specified, it defaults to
    ++`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
    ++`<upstream>` as direct ancestor will keep their original branch point, i.e.
    ++commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
    ++option will keep their original ancestry by default. In `rebase-cousins` mode,
    ++such commits are instead rebased onto `<upstream>` (or `<onto>`, if
    ++specified).
    + +
    + It is currently only possible to recreate the merge commits using the
    + `ort` merge strategy; different merge strategies can be used only via
     
      ## t/t3430-rebase-merges.sh ##
     @@ t/t3430-rebase-merges.sh: test_expect_success 'with a branch tip that was cherry-picked already' '
2:  c6099e6dee ! 2:  26f98b8400 rebase: deprecate --rebase-merges=""
    @@ Commit message
     
         The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
         empty string argument) has been an undocumented synonym of
    -    --rebase-merges=no-rebase-cousins. Deprecate that syntax to avoid
    -    confusion when a rebase.merges config option is introduced, where
    -    rebase.merges="" will be equivalent to --no-rebase-merges.
    +    --rebase-merges without an argument. Deprecate that syntax to avoid
    +    confusion when a rebase.rebaseMerges config option is introduced, where
    +    rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.
    +
    +    It is not likely that anyone is actually using this syntax, but just in
    +    case, deprecate the empty string argument instead of dropping support
    +    for it immediately.
     
         Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
     
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
     +			warning(_("--rebase-merges with an empty string "
     +				  "argument is deprecated and will stop "
     +				  "working in a future version of Git. Use "
    -+				  "--rebase-merges=no-rebase-cousins "
    -+				  "instead."));
    ++				  "--rebase-merges without an argument "
    ++				  "instead, which does the same thing."));
      		else if (!strcmp("rebase-cousins", rebase_merges))
      			options.rebase_cousins = 1;
      		else if (strcmp("no-rebase-cousins", rebase_merges))
    -
    - ## t/t3430-rebase-merges.sh ##
    -@@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless asked for' '
    - 	EOF
    - '
    - 
    -+test_expect_success '--rebase-merges="" is deprecated' '
    -+	git rebase --rebase-merges="" HEAD^ 2>actual &&
    -+	grep deprecated actual
    -+'
    -+
    - test_expect_success 'refs/rewritten/* is worktree-local' '
    - 	git worktree add wt &&
    - 	cat >wt/script-from-scratch <<-\EOF &&
3:  95cba9588c ! 3:  402365256c rebase: add a config option for --rebase-merges
    @@ Commit message
         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 possibly turning
    -    on --rebase-merges by default without configuration in a future version
    -    of Git.
    +    --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.
    +
    +    In the future, the default rebase-merges mode may change from
    +    no-rebase-cousins to rebase-cousins. Support setting rebase.rebaseMerges
    +    to the nonspecific value "true" for users who do not need or want to
    +    care about the default changing in the future. Similarly, for users who
    +    have --rebase-merges in an alias and want to get the future behavior
    +    now, use the specific rebase-merges mode from the config if a specific
    +    mode is not given on the command line.
     
         Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
     
    @@ Documentation/config/rebase.txt: rebase.rescheduleFailedExec::
      rebase.forkPoint::
      	If set to false set `--no-fork-point` option by default.
     +
    -+rebase.merges::
    ++rebase.rebaseMerges::
     +	Whether and how to set the `--rebase-merges` option by default. Can
     +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
     +	true is equivalent to `--rebase-merges` without an argument, setting to
     +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
     +	`--rebase-merges` with that value as its argument, and setting to false
     +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
    -+	command line without an argument overrides a `rebase.merges=false`
    -+	configuration but does not override other values of `rebase.merge`.
    ++	command line without an argument overrides a `rebase.rebaseMerges=false`
    ++	configuration, but the absence of a specific rebase-merges mode on the
    ++	command line does not counteract a specific mode set in the configuration.
     
      ## Documentation/git-rebase.txt ##
     @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
    @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
      	manual amendments in these merge commits will have to be
      	resolved/re-applied manually. `--no-rebase-merges` can be used to
     -	countermand a previous `--rebase-merges`.
    -+	countermand both the `rebase.merges` config option and a previous
    ++	countermand both the `rebase.rebaseMerges` config option and a previous
     +	`--rebase-merges`.
      +
    - By default, or when `no-rebase-cousins` was specified, commits which do not
    - have `<upstream>` as direct ancestor will keep their original branch point,
    + When rebasing merges, there are two modes: `rebase-cousins` and
    +-`no-rebase-cousins`. If the mode is not specified, it defaults to
    +-`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
    +-`<upstream>` as direct ancestor will keep their original branch point, i.e.
    +-commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
    +-option will keep their original ancestry by default. In `rebase-cousins` mode,
    +-such commits are instead rebased onto `<upstream>` (or `<onto>`, if
    +-specified).
    ++`no-rebase-cousins`. If the mode is not specified on the command line or in
    ++the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
    ++In `no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
    ++ancestor will keep their original branch point, i.e. commits that would be
    ++excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
    ++original ancestry by default. In `rebase-cousins` mode, such commits are
    ++instead rebased onto `<upstream>` (or `<onto>`, if specified).
    + +
    + It is currently only possible to recreate the merge commits using the
    + `ort` merge strategy; different merge strategies can be used only via
     
      ## builtin/rebase.c ##
     @@ builtin/rebase.c: struct rebase_options {
    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
      		return 0;
      	}
      
    -+	if (!strcmp(var, "rebase.merges")) {
    ++	if (!strcmp(var, "rebase.rebasemerges")) {
     +		opts->config_rebase_merges = git_parse_maybe_bool(value);
     +		if (opts->config_rebase_merges < 0) {
     +			opts->config_rebase_merges = 1;
    @@ builtin/rebase.c: static int parse_opt_empty(const struct option *opt, const cha
     +			warning(_("--rebase-merges with an empty string "
     +				  "argument is deprecated and will stop "
     +				  "working in a future version of Git. Use "
    -+				  "--rebase-merges=no-rebase-cousins "
    -+				  "instead."));
    -+			arg = "no-rebase-cousins";
    ++				  "--rebase-merges without an argument "
    ++				  "instead, which does the same thing."));
    ++			return 0;
     +		}
     +		parse_rebase_merges_value(options, arg);
     +	}
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
     -			warning(_("--rebase-merges with an empty string "
     -				  "argument is deprecated and will stop "
     -				  "working in a future version of Git. Use "
    --				  "--rebase-merges=no-rebase-cousins "
    --				  "instead."));
    +-				  "--rebase-merges without an argument "
    +-				  "instead, which does the same thing."));
     -		else if (!strcmp("rebase-cousins", rebase_merges))
     -			options.rebase_cousins = 1;
     -		else if (strcmp("no-rebase-cousins", rebase_merges))
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
     +			if (options.autosquash == -1 && options.config_autosquash == 1)
      				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
     +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
    -+				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
    ++				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
      			else if (options.update_refs == -1 && options.config_update_refs == 1)
      				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
     +			else if (is_merge(&options))
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
      	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
     
      ## t/t3422-rebase-incompatible-options.sh ##
    +@@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
    + 		test_must_fail git rebase $opt --reapply-cherry-picks A
    + 	"
    + 
    ++	test_expect_success "$opt incompatible with --rebase-merges" "
    ++		git checkout B^0 &&
    ++		test_must_fail git rebase $opt --rebase-merges A
    ++	"
    ++
    + 	test_expect_success "$opt incompatible with --update-refs" "
    + 		git checkout B^0 &&
    + 		test_must_fail git rebase $opt --update-refs A
     @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
      		grep -e --no-autosquash err
      	"
      
    -+	test_expect_success "$opt incompatible with rebase.merges" "
    ++	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
     +		git checkout B^0 &&
    -+		test_must_fail git -c rebase.merges=true rebase $opt A 2>err &&
    ++		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
     +		grep -e --no-rebase-merges err
     +	"
     +
    @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
      		git -c rebase.autosquash=true rebase --no-autosquash $opt A
      	"
      
    -+	test_expect_success "$opt okay with overridden rebase.merges" "
    ++	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
     +		test_when_finished \"git reset --hard B^0\" &&
     +		git checkout B^0 &&
    -+		git -c rebase.merges=true rebase --no-rebase-merges $opt A
    ++		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
     +	"
     +
      	test_expect_success "$opt okay with overridden rebase.updateRefs" "
    @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
      		git checkout B^0 &&
     
      ## t/t3430-rebase-merges.sh ##
    -@@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated' '
    - 	grep deprecated actual
    +@@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless asked for' '
    + 	EOF
      '
      
    -+test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
    -+	test_config rebase.merges rebase-cousins &&
    ++test_expect_success '--rebase-merges="" is deprecated' '
    ++	git rebase --rebase-merges="" HEAD^ 2>actual &&
    ++	grep deprecated actual
    ++'
    ++
    ++test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
    ++	test_config rebase.rebaseMerges rebase-cousins &&
     +	git checkout -b config-rebase-cousins main &&
     +	git rebase HEAD^ &&
     +	test_cmp_graph HEAD^.. <<-\EOF
    @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
     +	EOF
     +'
     +
    -+test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
    -+	test_config rebase.merges no-rebase-cousins &&
    ++test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
    ++	test_config rebase.rebaseMerges no-rebase-cousins &&
     +	git checkout -b override-config-no-rebase-cousins E &&
     +	git rebase --no-rebase-merges C &&
     +	test_cmp_graph C.. <<-\EOF
    @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
     +	EOF
     +'
     +
    -+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
    -+	test_config rebase.merges rebase-cousins &&
    ++test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.rebaseMerges=rebase-cousins' '
    ++	test_config rebase.rebaseMerges rebase-cousins &&
     +	git checkout -b override-config-rebase-cousins main &&
     +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
     +	test_cmp_graph HEAD^.. <<-\EOF
    @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
     +	EOF
     +'
     +
    -+test_expect_success '--rebase-merges overrides rebase.merges=false' '
    -+	test_config rebase.merges false &&
    ++test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
    ++	test_config rebase.rebaseMerges false &&
     +	git checkout -b override-config-merges-false E &&
     +	before="$(git rev-parse --verify HEAD)" &&
     +	test_tick &&
    @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
     +	test_cmp_rev HEAD $before
     +'
     +
    -+test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
    -+	test_config rebase.merges rebase-cousins &&
    ++test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
    ++	test_config rebase.rebaseMerges rebase-cousins &&
     +	git checkout -b no-override-config-rebase-cousins main &&
     +	git rebase --rebase-merges HEAD^ &&
     +	test_cmp_graph HEAD^.. <<-\EOF
-- 
2.39.2
Alex Henrie· Mar 5, 2023, 05:07 UTC · re: Alex Henrie · lore

[PATCH v6 3/3] 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.

In the future, the default rebase-merges mode may change from no-rebase-cousins to rebase-cousins. Support setting rebase.rebaseMerges to the nonspecific value "true" for users who do not need or want to care about the default changing in the future. Similarly, for users who have --rebase-merges in an alias and want to get the future behavior now, use the specific rebase-merges mode from the config if a specific mode is not given on the command line.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/config/rebase.txt        | 11 ++++
 Documentation/git-rebase.txt           | 17 +++---
 builtin/rebase.c                       | 79 ++++++++++++++++++--------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 68 ++++++++++++++++++++++
 5 files changed, 161 insertions(+), 31 deletions(-)
Show changes to 5 files +161 −31

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3422-rebase-incompatible-options.sh, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..f7d3218b1d 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,14 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.rebaseMerges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true is equivalent to `--rebase-merges` without an argument, setting to
+	`rebase-cousins` or `no-rebase-cousins` is equivalent to
+	`--rebase-merges` with that value as its argument, and setting to false
+	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line without an argument overrides a `rebase.rebaseMerges=false`
+	configuration, but the absence of a specific rebase-merges mode on the
+	command line does not counteract a specific mode set in the configuration.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4e57a87624..6ec86c9c6e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,16 +537,17 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.rebaseMerges` config option and a previous
+	`--rebase-merges`.
 +
 When rebasing merges, there are two modes: `rebase-cousins` and
-`no-rebase-cousins`. If the mode is not specified, it defaults to
-`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
-`<upstream>` as direct ancestor will keep their original branch point, i.e.
-commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
-option will keep their original ancestry by default. In `rebase-cousins` mode,
-such commits are instead rebased onto `<upstream>` (or `<onto>`, if
-specified).
+`no-rebase-cousins`. If the mode is not specified on the command line or in
+the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
+In `no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
+ancestor will keep their original branch point, i.e. commits that would be
+excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
+original ancestry by default. In `rebase-cousins` mode, such commits are
+instead rebased onto `<upstream>` (or `<onto>`, if specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c36ddc0050..04f815e3d0 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -123,6 +123,7 @@ struct rebase_options {
 	int fork_point;
 	int update_refs;
 	int config_autosquash;
+	int config_rebase_merges;
 	int config_update_refs;
 };
 
@@ -140,6 +141,8 @@ struct rebase_options {
 		.allow_empty_message = 1,               \
 		.autosquash = -1,                       \
 		.config_autosquash = -1,                \
+		.rebase_merges = -1,                    \
+		.config_rebase_merges = -1,             \
 		.update_refs = -1,                      \
 		.config_update_refs = -1,               \
 	}
@@ -771,6 +774,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+	if (!strcmp("no-rebase-cousins", value))
+		options->rebase_cousins = 0;
+	else if (!strcmp("rebase-cousins", value))
+		options->rebase_cousins = 1;
+	else
+		die(_("Unknown rebase-merges mode: %s"), value);
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -800,6 +813,15 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.rebasemerges")) {
+		opts->config_rebase_merges = git_parse_maybe_bool(value);
+		if (opts->config_rebase_merges < 0) {
+			opts->config_rebase_merges = 1;
+			parse_rebase_merges_value(opts, value);
+		}
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.updaterefs")) {
 		opts->config_update_refs = git_config_bool(var, value);
 		return 0;
@@ -980,6 +1002,27 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	options->rebase_merges = !unset;
+
+	if (arg) {
+		if (!*arg) {
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
+			return 0;
+		}
+		parse_rebase_merges_value(options, arg);
+	}
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1035,7 +1078,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1137,10 +1179,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1436,21 +1477,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!*rebase_merges)
-			warning(_("--rebase-merges with an empty string "
-				  "argument is deprecated and will stop "
-				  "working in a future version of Git. Use "
-				  "--rebase-merges without an argument "
-				  "instead, which does the same thing."));
-		else if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
-		imply_merge(&options, "--rebase-merges");
-	}
-
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
 			strvec_push(&options.git_am_opts,
@@ -1513,13 +1539,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				break;
 
 		if (i >= 0 || options.type == REBASE_APPLY) {
-			if (is_merge(&options))
-				die(_("apply options and merge options "
-					  "cannot be used together"));
-			else if (options.autosquash == -1 && options.config_autosquash == 1)
+			if (options.autosquash == -1 && options.config_autosquash == 1)
 				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
+			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
 			else if (options.update_refs == -1 && options.config_update_refs == 1)
 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
+			else if (is_merge(&options))
+				die(_("apply options and merge options "
+					  "cannot be used together"));
 			else
 				options.type = REBASE_APPLY;
 		}
@@ -1530,6 +1558,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
+	if (options.rebase_merges == 1)
+		imply_merge(&options, "--rebase-merges");
+	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
 	if (options.autosquash == 1)
 		imply_merge(&options, "--autosquash");
 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index 4711b37a28..2eba00bdf5 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -85,6 +85,11 @@ test_rebase_am_only () {
 		test_must_fail git rebase $opt --reapply-cherry-picks A
 	"
 
+	test_expect_success "$opt incompatible with --rebase-merges" "
+		git checkout B^0 &&
+		test_must_fail git rebase $opt --rebase-merges A
+	"
+
 	test_expect_success "$opt incompatible with --update-refs" "
 		git checkout B^0 &&
 		test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
 		grep -e --no-autosquash err
 	"
 
+	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
+		git checkout B^0 &&
+		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
+		grep -e --no-rebase-merges err
+	"
+
 	test_expect_success "$opt incompatible with rebase.updateRefs" "
 		git checkout B^0 &&
 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
 	"
 
+	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
+		test_when_finished \"git reset --hard B^0\" &&
+		git checkout B^0 &&
+		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
+	"
+
 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
 		test_when_finished \"git reset --hard B^0\" &&
 		git checkout B^0 &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..aa75e192d1 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,74 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success '--rebase-merges="" is deprecated' '
+	git rebase --rebase-merges="" HEAD^ 2>actual &&
+	grep deprecated actual
+'
+
+test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
+	test_config rebase.rebaseMerges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.rebaseMerges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b override-config-rebase-cousins main &&
+	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	o | H
+	|/
+	o A
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
+	test_config rebase.rebaseMerges false &&
+	git checkout -b override-config-merges-false E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
+test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b no-override-config-rebase-cousins main &&
+	git rebase --rebase-merges HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Phillip Wood· Mar 7, 2023, 14:56 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 05/03/2023 05:07, Alex Henrie wrote:
Show 40 quoted lines
> 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.
> 
> In the future, the default rebase-merges mode may change from
> no-rebase-cousins to rebase-cousins. Support setting rebase.rebaseMerges
> to the nonspecific value "true" for users who do not need or want to
> care about the default changing in the future. Similarly, for users who
> have --rebase-merges in an alias and want to get the future behavior
> now, use the specific rebase-merges mode from the config if a specific
> mode is not given on the command line.
> 
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
>   Documentation/config/rebase.txt        | 11 ++++
>   Documentation/git-rebase.txt           | 17 +++---
>   builtin/rebase.c                       | 79 ++++++++++++++++++--------
>   t/t3422-rebase-incompatible-options.sh | 17 ++++++
>   t/t3430-rebase-merges.sh               | 68 ++++++++++++++++++++++
>   5 files changed, 161 insertions(+), 31 deletions(-)
> 
> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
> index f19bd0e040..f7d3218b1d 100644
> --- a/Documentation/config/rebase.txt
> +++ b/Documentation/config/rebase.txt
> @@ -67,3 +67,14 @@ rebase.rescheduleFailedExec::
>   
>   rebase.forkPoint::
>   	If set to false set `--no-fork-point` option by default.
> +
> +rebase.rebaseMerges::
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true is equivalent to `--rebase-merges` without an argument,

This is a bit picky but how can rebase.rebaseMerges=true be equivalent to --rebase-merges without an argument when the behavior of --rebase-merges without an argument depends on the value of rebase.rebaseMerges?

Show 7 quoted lines
> setting to
> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
> +	`--rebase-merges` with that value as its argument, and setting to false
> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line without an argument overrides a `rebase.rebaseMerges=false`
> +	configuration, but the absence of a specific rebase-merges mode on the
> +	command line does not counteract a specific mode set in the configuration.

I may not agree the with the design choice but the documentation here and below is very clear about the behavior of --rebase-merges without an argument which is good.

Show 27 quoted lines
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index 4e57a87624..6ec86c9c6e 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -537,16 +537,17 @@ See also INCOMPATIBLE OPTIONS below.
>   	by recreating the merge commits. Any resolved merge conflicts or
>   	manual amendments in these merge commits will have to be
>   	resolved/re-applied manually. `--no-rebase-merges` can be used to
> -	countermand a previous `--rebase-merges`.
> +	countermand both the `rebase.rebaseMerges` config option and a previous
> +	`--rebase-merges`.
>   +
>   When rebasing merges, there are two modes: `rebase-cousins` and
> -`no-rebase-cousins`. If the mode is not specified, it defaults to
> -`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
> -`<upstream>` as direct ancestor will keep their original branch point, i.e.
> -commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
> -option will keep their original ancestry by default. In `rebase-cousins` mode,
> -such commits are instead rebased onto `<upstream>` (or `<onto>`, if
> -specified).
> +`no-rebase-cousins`. If the mode is not specified on the command line or in
> +the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
> +In `no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
> +ancestor will keep their original branch point, i.e. commits that would be
> +excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
> +original ancestry by default. In `rebase-cousins` mode, such commits are
> +instead rebased onto `<upstream>` (or `<onto>`, if specified).
 >[...]
Show 13 quoted lines
>   static int rebase_config(const char *var, const char *value, void *data)
>   {
>   	struct rebase_options *opts = data;
> @@ -800,6 +813,15 @@ static int rebase_config(const char *var, const char *value, void *data)
>   		return 0;
>   	}
>   
> +	if (!strcmp(var, "rebase.rebasemerges")) {
> +		opts->config_rebase_merges = git_parse_maybe_bool(value);
> +		if (opts->config_rebase_merges < 0) {
> +			opts->config_rebase_merges = 1;
> +			parse_rebase_merges_value(opts, value);
> +		}
I think we need
	} else {
		opts->rebase_cousins = 0;
	}

here. Otherwise if rebase.rebaseMerges is set twice we wont follow the usual "last config wins" convention. For example

	[rebase]
		rebaseMerges=rebase-cousins
		rebaseMerges=true
will result in us unexpectedly rebasing cousins
The rest of the patch looks fine
Best Wishes
Phillip
Show 230 quoted lines
> +		return 0;
> +	}
> +
>   	if (!strcmp(var, "rebase.updaterefs")) {
>   		opts->config_update_refs = git_config_bool(var, value);
>   		return 0;
> @@ -980,6 +1002,27 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>   	return 0;
>   }
>   
> +static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +
> +	options->rebase_merges = !unset;
> +
> +	if (arg) {
> +		if (!*arg) {
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges without an argument "
> +				  "instead, which does the same thing."));
> +			return 0;
> +		}
> +		parse_rebase_merges_value(options, arg);
> +	}
> +
> +	return 0;
> +}
> +
>   static void NORETURN error_on_missing_default_upstream(void)
>   {
>   	struct branch *current_branch = branch_get(NULL);
> @@ -1035,7 +1078,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	struct object_id branch_base;
>   	int ignore_whitespace = 0;
>   	const char *gpg_sign = NULL;
> -	const char *rebase_merges = NULL;
>   	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
>   	struct object_id squash_onto;
>   	char *squash_onto_name = NULL;
> @@ -1137,10 +1179,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   			   &options.allow_empty_message,
>   			   N_("allow rebasing commits with empty messages"),
>   			   PARSE_OPT_HIDDEN),
> -		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
> -			N_("mode"),
> +		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
>   			N_("try to rebase merges instead of skipping them"),
> -			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
> +			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
>   		OPT_BOOL(0, "fork-point", &options.fork_point,
>   			 N_("use 'merge-base --fork-point' to refine upstream")),
>   		OPT_STRING('s', "strategy", &options.strategy,
> @@ -1436,21 +1477,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	if (options.exec.nr)
>   		imply_merge(&options, "--exec");
>   
> -	if (rebase_merges) {
> -		if (!*rebase_merges)
> -			warning(_("--rebase-merges with an empty string "
> -				  "argument is deprecated and will stop "
> -				  "working in a future version of Git. Use "
> -				  "--rebase-merges without an argument "
> -				  "instead, which does the same thing."));
> -		else if (!strcmp("rebase-cousins", rebase_merges))
> -			options.rebase_cousins = 1;
> -		else if (strcmp("no-rebase-cousins", rebase_merges))
> -			die(_("Unknown mode: %s"), rebase_merges);
> -		options.rebase_merges = 1;
> -		imply_merge(&options, "--rebase-merges");
> -	}
> -
>   	if (options.type == REBASE_APPLY) {
>   		if (ignore_whitespace)
>   			strvec_push(&options.git_am_opts,
> @@ -1513,13 +1539,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   				break;
>   
>   		if (i >= 0 || options.type == REBASE_APPLY) {
> -			if (is_merge(&options))
> -				die(_("apply options and merge options "
> -					  "cannot be used together"));
> -			else if (options.autosquash == -1 && options.config_autosquash == 1)
> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>   				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
> +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>   			else if (options.update_refs == -1 && options.config_update_refs == 1)
>   				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
> +			else if (is_merge(&options))
> +				die(_("apply options and merge options "
> +					  "cannot be used together"));
>   			else
>   				options.type = REBASE_APPLY;
>   		}
> @@ -1530,6 +1558,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
>   			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
>   
> +	if (options.rebase_merges == 1)
> +		imply_merge(&options, "--rebase-merges");
> +	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
> +				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
> +
>   	if (options.autosquash == 1)
>   		imply_merge(&options, "--autosquash");
>   	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
> diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
> index 4711b37a28..2eba00bdf5 100755
> --- a/t/t3422-rebase-incompatible-options.sh
> +++ b/t/t3422-rebase-incompatible-options.sh
> @@ -85,6 +85,11 @@ test_rebase_am_only () {
>   		test_must_fail git rebase $opt --reapply-cherry-picks A
>   	"
>   
> +	test_expect_success "$opt incompatible with --rebase-merges" "
> +		git checkout B^0 &&
> +		test_must_fail git rebase $opt --rebase-merges A
> +	"
> +
>   	test_expect_success "$opt incompatible with --update-refs" "
>   		git checkout B^0 &&
>   		test_must_fail git rebase $opt --update-refs A
> @@ -101,6 +106,12 @@ test_rebase_am_only () {
>   		grep -e --no-autosquash err
>   	"
>   
> +	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
> +		git checkout B^0 &&
> +		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
> +		grep -e --no-rebase-merges err
> +	"
> +
>   	test_expect_success "$opt incompatible with rebase.updateRefs" "
>   		git checkout B^0 &&
>   		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
> @@ -113,6 +124,12 @@ test_rebase_am_only () {
>   		git -c rebase.autosquash=true rebase --no-autosquash $opt A
>   	"
>   
> +	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
> +		test_when_finished \"git reset --hard B^0\" &&
> +		git checkout B^0 &&
> +		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
> +	"
> +
>   	test_expect_success "$opt okay with overridden rebase.updateRefs" "
>   		test_when_finished \"git reset --hard B^0\" &&
>   		git checkout B^0 &&
> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
> index d46d9545f2..aa75e192d1 100755
> --- a/t/t3430-rebase-merges.sh
> +++ b/t/t3430-rebase-merges.sh
> @@ -278,6 +278,74 @@ test_expect_success 'do not rebase cousins unless asked for' '
>   	EOF
>   '
>   
> +test_expect_success '--rebase-merges="" is deprecated' '
> +	git rebase --rebase-merges="" HEAD^ 2>actual &&
> +	grep deprecated actual
> +'
> +
> +test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
> +	test_config rebase.rebaseMerges rebase-cousins &&
> +	git checkout -b config-rebase-cousins main &&
> +	git rebase HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
> +test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
> +	test_config rebase.rebaseMerges no-rebase-cousins &&
> +	git checkout -b override-config-no-rebase-cousins E &&
> +	git rebase --no-rebase-merges C &&
> +	test_cmp_graph C.. <<-\EOF
> +	* B
> +	* D
> +	o C
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.rebaseMerges=rebase-cousins' '
> +	test_config rebase.rebaseMerges rebase-cousins &&
> +	git checkout -b override-config-rebase-cousins main &&
> +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	o | H
> +	|/
> +	o A
> +	EOF
> +'
> +
> +test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
> +	test_config rebase.rebaseMerges false &&
> +	git checkout -b override-config-merges-false E &&
> +	before="$(git rev-parse --verify HEAD)" &&
> +	test_tick &&
> +	git rebase --rebase-merges C &&
> +	test_cmp_rev HEAD $before
> +'
> +
> +test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
> +	test_config rebase.rebaseMerges rebase-cousins &&
> +	git checkout -b no-override-config-rebase-cousins main &&
> +	git rebase --rebase-merges HEAD^ &&
> +	test_cmp_graph HEAD^.. <<-\EOF
> +	*   Merge the topic branch '\''onebranch'\''
> +	|\
> +	| * D
> +	| * G
> +	|/
> +	o H
> +	EOF
> +'
> +
>   test_expect_success 'refs/rewritten/* is worktree-local' '
>   	git worktree add wt &&
>   	cat >wt/script-from-scratch <<-\EOF &&
Junio C Hamano· Mar 7, 2023, 18:32 UTC · re: Phillip Wood · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 9 quoted lines
>> +rebase.rebaseMerges::
>> +	Whether and how to set the `--rebase-merges` option by default. Can
>> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
>> +	true is equivalent to `--rebase-merges` without an argument,
>
> This is a bit picky but how can rebase.rebaseMerges=true be equivalent
> to --rebase-merges without an argument when the behavior of
> --rebase-merges without an argument depends on the value of
> rebase.rebaseMerges?

True. I think the configuration is meant to give (when set to something other than Boolean) the default value to the option "--rebase-merges" that is given without value, so setting to false should be a no-op (a command line option would override it if given, and if there is no command line option, --rebase-merges is not used by default), setting it to a specific value between cousin choices would give --rebase-merges=<value> unless --no-rebase-merges is given, but setting it to true here makes the result undefined, unless the built-in default between cousin choices is described here.

"Setting to true is equivalent to setting to no-rebase-cousins" and "Setting to false is a no-op but accepted only for completeness", perhaps?

Show 11 quoted lines
>> setting to
>> +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
>> +	`--rebase-merges` with that value as its argument, and setting to false
>> +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
>> +	command line without an argument overrides a `rebase.rebaseMerges=false`
>> +	configuration, but the absence of a specific rebase-merges mode on the
>> +	command line does not counteract a specific mode set in the configuration.
>
> I may not agree the with the design choice but the documentation here
> and below is very clear about the behavior of --rebase-merges without
> an argument which is good.

Is it? rebase.rebaseMerges=true does not give "a specific mode set in the configuration", so it still is unclear what --rebase-merges should do in that case. Unless what it means to set it to true is described, as you pointed out above, that is.

>> +`no-rebase-cousins`. If the mode is not specified on the command line or in
>> +the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.

This side could describe what setting it to "true" means, but it is a separate page so it would be more friendly to readers to cover it on both pages.

Show 14 quoted lines
> I think we need
>
> 	} else {
> 		opts->rebase_cousins = 0;
> 	}
>
> here. Otherwise if rebase.rebaseMerges is set twice we wont follow the
> usual "last config wins" convention. For example
>
> 	[rebase]
> 		rebaseMerges=rebase-cousins
> 		rebaseMerges=true
>
> will result in us unexpectedly rebasing cousins
Thanks for a careful review.
Alex Henrie· Mar 12, 2023, 21:01 UTC · re: Junio C Hamano · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Tue, Mar 7, 2023 at 11:32 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 27 quoted lines
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> >> +rebase.rebaseMerges::
> >> +    Whether and how to set the `--rebase-merges` option by default. Can
> >> +    be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> >> +    true is equivalent to `--rebase-merges` without an argument,
> >
> > This is a bit picky but how can rebase.rebaseMerges=true be equivalent
> > to --rebase-merges without an argument when the behavior of
> > --rebase-merges without an argument depends on the value of
> > rebase.rebaseMerges?
>
> True.  I think the configuration is meant to give (when set to
> something other than Boolean) the default value to the option
> "--rebase-merges" that is given without value, so setting to false
> should be a no-op (a command line option would override it if given,
> and if there is no command line option, --rebase-merges is not used
> by default), setting it to a specific value between cousin choices
> would give --rebase-merges=<value> unless --no-rebase-merges is
> given, but setting it to true here makes the result undefined,
> unless the built-in default between cousin choices is described
> here.
>
> "Setting to true is equivalent to setting to no-rebase-cousins" and
> "Setting to false is a no-op but accepted only for completeness",
> perhaps?

A false in the local configuration overrides a true in the global configuration, so I wouldn't call "false" a no-op. But it would be fine to say that "true" is equivalent to no-rebase-cousins (and then change the documentation for "true" if and when the default changes in the future).

Show 6 quoted lines
> >> +`no-rebase-cousins`. If the mode is not specified on the command line or in
> >> +the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
>
> This side could describe what setting it to "true" means, but it is
> a separate page so it would be more friendly to readers to cover it
> on both pages.

The longer the documentation is, the less likely people are to read any of it. I don't really want to repeat the detailed explanation that is in git-config.txt, but I see that other places in git-rebase.txt link to git-config.txt, and having a link seems like a good idea. Fair enough?

-Alex
Glen Choo· Mar 8, 2023, 00:09 UTC · re: Phillip Wood · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 25 quoted lines
>> @@ -800,6 +813,15 @@ static int rebase_config(const char *var, const char *value, void *data)
>>   		return 0;
>>   	}
>>   
>> +	if (!strcmp(var, "rebase.rebasemerges")) {
>> +		opts->config_rebase_merges = git_parse_maybe_bool(value);
>> +		if (opts->config_rebase_merges < 0) {
>> +			opts->config_rebase_merges = 1;
>> +			parse_rebase_merges_value(opts, value);
>> +		}
>
> I think we need
>
> 	} else {
> 		opts->rebase_cousins = 0;
> 	}
>
> here. Otherwise if rebase.rebaseMerges is set twice we wont follow the 
> usual "last config wins" convention. For example
>
> 	[rebase]
> 		rebaseMerges=rebase-cousins
> 		rebaseMerges=true
>
> will result in us unexpectedly rebasing cousins
Ah, good catch.

An alternative might be to use the "lookup" functions (e.g. repo_config_get_maybe_bool()), which already implement "last one wins" semantics. It's a bigger change, but it may be easier for future readers to understand. I don't have a strong opinion on which is the 'better' approach.

Glen Choo· Mar 8, 2023, 00:02 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 9 quoted lines
> 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.
This rationale makes sense to me.
> In the future, the default rebase-merges mode may change from
> no-rebase-cousins to rebase-cousins.

I suspect a more likely future would be that the default is to rebase 'evil' merges instead of trying to recreate merge commits, but of course, the important thing is that we promote the default, not what the default will be...

Show 6 quoted lines
>                                      Support setting rebase.rebaseMerges
> to the nonspecific value "true" for users who do not need or want to
> care about the default changing in the future. Similarly, for users who
> have --rebase-merges in an alias and want to get the future behavior
> now, use the specific rebase-merges mode from the config if a specific
> mode is not given on the command line.
so this rationale makes sense to me too :)
Show 8 quoted lines
> @@ -278,6 +278,74 @@ test_expect_success 'do not rebase cousins unless asked for' '
>  	EOF
>  '
>  
> +test_expect_success '--rebase-merges="" is deprecated' '
> +	git rebase --rebase-merges="" HEAD^ 2>actual &&
> +	grep deprecated actual
> +'
I believe this used to be on 2/3, i.e.
  https://lore.kernel.org/git/20230225180325.796624-3-alexhenrie24@gmail.com/
but your cover letter suggests that it was removed. Mechanical error?
The rest of changes look good (besides what others have spotted).
Alex Henrie· Mar 12, 2023, 21:03 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Tue, Mar 7, 2023 at 5:02 PM Glen Choo <chooglen@google.com> wrote:
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
Show 10 quoted lines
> > +test_expect_success '--rebase-merges="" is deprecated' '
> > +     git rebase --rebase-merges="" HEAD^ 2>actual &&
> > +     grep deprecated actual
> > +'
>
> I believe this used to be on 2/3, i.e.
>
>   https://lore.kernel.org/git/20230225180325.796624-3-alexhenrie24@gmail.com/
>
> but your cover letter suggests that it was removed. Mechanical error?

I removed it from the second patch but accidentally added it back in the third patch. Thanks for catching that.

-Alex
Alex Henrie· Mar 15, 2023, 02:52 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Tue, Mar 7, 2023 at 5:02 PM Glen Choo <chooglen@google.com> wrote:
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
Show 7 quoted lines
> > In the future, the default rebase-merges mode may change from
> > no-rebase-cousins to rebase-cousins.
>
> I suspect a more likely future would be that the default is to rebase
> 'evil' merges instead of trying to recreate merge commits, but of
> course, the important thing is that we promote the default, not what the
> default will be...

Glen, do you have any more thoughts? At this point, the only thing that's keeping me from implementing Phillip's request to make --rebase-merges without an argument clobber rebase.rebaseMerges is your suspicion that we might need to change the default rebase-merges mode in the future, and I assume that we would want to use the rebase.rebaseMerges config option to facilitate the transition.

-Alex
Glen Choo· Mar 16, 2023, 17:32 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 14 quoted lines
>> > In the future, the default rebase-merges mode may change from
>> > no-rebase-cousins to rebase-cousins.
>>
>> I suspect a more likely future would be that the default is to rebase
>> 'evil' merges instead of trying to recreate merge commits, but of
>> course, the important thing is that we promote the default, not what the
>> default will be...
>
> Glen, do you have any more thoughts? At this point, the only thing
> that's keeping me from implementing Phillip's request to make
> --rebase-merges without an argument clobber rebase.rebaseMerges is
> your suspicion that we might need to change the default rebase-merges
> mode in the future, and I assume that we would want to use the
> rebase.rebaseMerges config option to facilitate the transition.
(Sorry for the late reply)

Ah, I don't really have more thoughts on the matter. I am fairly confident that we would _like_ to change the default to rebase 'evil' merges, but I don't know how likely that will be.

Perhaps it would help to enumerate the rules to see if it is too confusing or not?

The behaviors we can tweak are:
- Whether to rebase merges or not (true, false, specified mode, or
  default)
- What mode to use when rebasing merges (specified mode or default)

And the sources are either CLI or config, with CLI always overriding config.

Should we rebase a merge?
- If neither CLI or config tells us whether or not to rebase a merge,
  default to "don't rebase merges".
- If one of CLI or config tells us whether or not to rebase a merge,
  respect it.
- If both CLI or config tell us whether or not to rebase a merge,
  respect CLI and ignore config.
What mode should we use?
- If neither CLI or config tells us what mode to use, default to
  "no-rebase-cousins" (or whatever default we decide).
- If one of CLI or config tells us what mode to use, respect it.
- If both CLI or config tell us what mode to use, respect CLI and ignore
  config.

If users cleanly separate the two concepts, I think it is quite clear. (I'm not advocating for this approach, but) e.g. if we pretend that each behavior were configured separately, like:

--[no-]rebase-merges [--rebase-merges-mode=(rebase-cousins|no-rebase-cousins)]

I don't think there would be any confusion. (Having --rebase-merges-mode be a no-op without --rebase-merges is probably even more confusing to users, plus this would break backwards compatibility, so I don't think this is a good idea at all.)

Your doc patch explains the rules pretty clearly, but perhaps it doesn't explain this mental model clearly enough, hence the confusion. If we don't find a good way to communicate this (I think it is clear, but other reviewers seem yet unconvinced), I wouldn't mind taking Phillip's suggestion to have "--rebase-merges" override "rebase.rebaseMerges='specific-mode'".

>
> -Alex
Felipe Contreras· Mar 16, 2023, 18:11 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 16, 2023 at 11:57 AM Glen Choo <chooglen@google.com> wrote:
Show 7 quoted lines
> If users cleanly separate the two concepts, I think it is quite clear.
> (I'm not advocating for this approach, but) e.g. if we pretend that each
> behavior were configured separately, like:
>
> --[no-]rebase-merges [--rebase-merges-mode=(rebase-cousins|no-rebase-cousins)]
>
> I don't think there would be any confusion.
Not being conversant with these options I agree the above isn't confusing.
> (Having --rebase-merges-mode
> be a no-op without --rebase-merges is probably even more confusing to
> users, plus this would break backwards compatibility, so I don't think
> this is a good idea at all.)

I don't find it confusing. And how would it break backwards compatibility if --rebase-merges-mode doesn't exist now?

-- 
Felipe Contreras
Glen Choo· Mar 16, 2023, 22:46 UTC · re: Felipe Contreras · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Felipe Contreras <felipe.contreras@gmail.com> writes:
Show 11 quoted lines
>> --[no-]rebase-merges [--rebase-merges-mode=(rebase-cousins|no-rebase-cousins)]
>>
>> I don't think there would be any confusion.
>> [...]
>> (Having --rebase-merges-mode
>> be a no-op without --rebase-merges is probably even more confusing to
>> users, plus this would break backwards compatibility, so I don't think
>> this is a good idea at all.)
>
> I don't find it confusing. And how would it break backwards
> compatibility if --rebase-merges-mode doesn't exist now?

I meant that for the above example to work, we would need to have `--[no-]rebase-merges` as a boolean that says whether we rebase merges at all, and `--rebase-merges-mode=[whatever]` would tell us what mode to use _if_ we were rebasing merges. This means that `--rebase-merges=not-a-boolean` would become invalid.

We were in this position before, actually, with `git log -p -m`. The conclusion on a recent series [1] is that having such a no-op flag was probably a mistake (and unfortunately, one that is extremely hard to fix due to backwards compatibility requirements), so introducing more no-op flags is probably a bad idea :)

[1] https://lore.kernel.org/git/871qn5pyez.fsf@osv.gnss.ru
Felipe Contreras· Mar 16, 2023, 23:48 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 16, 2023 at 4:46 PM Glen Choo <chooglen@google.com> wrote:
Show 19 quoted lines
>
> Felipe Contreras <felipe.contreras@gmail.com> writes:
>
> >> --[no-]rebase-merges [--rebase-merges-mode=(rebase-cousins|no-rebase-cousins)]
> >>
> >> I don't think there would be any confusion.
> >> [...]
> >> (Having --rebase-merges-mode
> >> be a no-op without --rebase-merges is probably even more confusing to
> >> users, plus this would break backwards compatibility, so I don't think
> >> this is a good idea at all.)
> >
> > I don't find it confusing. And how would it break backwards
> > compatibility if --rebase-merges-mode doesn't exist now?
>
> I meant that for the above example to work, we would need to have
> `--[no-]rebase-merges` as a boolean that says whether we rebase merges at
> all, and `--rebase-merges-mode=[whatever]` would tell us what mode to
> use _if_ we were rebasing merges.

No, we don't need --no-rebase-merges for --rebase-merges-mode to work. It would be nice, but not necessary.

> This means that
> `--rebase-merges=not-a-boolean` would become invalid.

No, it would not become invalid, that's yet another decision to make. `--rebase-merges=mode` could become synonymous with `--rebase-merges --rebase-merges-mode=mode`. A shortcut.

Because it's redundant it could be deprecated in the future, but not necessarily invalid.

> We were in this position before, actually, with `git log -p -m`. The
> conclusion on a recent series [1] is that having such a no-op flag was
> probably a mistake (and unfortunately, one that is extremely hard to fix
> due to backwards compatibility requirements),

No, `git log -m` was a mistake *only* if -p isn't turned on as well, which is an interface wart that could be fixed today.

> so introducing more no-op flags is probably a bad idea :)

Whether --rebase-merges-mode turns on --rebase-merges by default is a decision that could be made in the future. Just like `git log -m` turning on `-p` by default is a decision that could be made in the future (and probably should).

---

Either way: introducing a new option cannot possibly break backwards compatibility.

Cheers.
-- 
Felipe Contreras
Alex Henrie· Mar 16, 2023, 20:27 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 16, 2023 at 11:32 AM Glen Choo <chooglen@google.com> wrote:
Show 70 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> >> > In the future, the default rebase-merges mode may change from
> >> > no-rebase-cousins to rebase-cousins.
> >>
> >> I suspect a more likely future would be that the default is to rebase
> >> 'evil' merges instead of trying to recreate merge commits, but of
> >> course, the important thing is that we promote the default, not what the
> >> default will be...
> >
> > Glen, do you have any more thoughts? At this point, the only thing
> > that's keeping me from implementing Phillip's request to make
> > --rebase-merges without an argument clobber rebase.rebaseMerges is
> > your suspicion that we might need to change the default rebase-merges
> > mode in the future, and I assume that we would want to use the
> > rebase.rebaseMerges config option to facilitate the transition.
>
> (Sorry for the late reply)
>
> Ah, I don't really have more thoughts on the matter. I am fairly
> confident that we would _like_ to change the default to rebase 'evil'
> merges, but I don't know how likely that will be.
>
> Perhaps it would help to enumerate the rules to see if it is too
> confusing or not?
>
> The behaviors we can tweak are:
>
> - Whether to rebase merges or not (true, false, specified mode, or
>   default)
> - What mode to use when rebasing merges (specified mode or default)
>
> And the sources are either CLI or config, with CLI always overriding
> config.
>
> Should we rebase a merge?
>
> - If neither CLI or config tells us whether or not to rebase a merge,
>   default to "don't rebase merges".
> - If one of CLI or config tells us whether or not to rebase a merge,
>   respect it.
> - If both CLI or config tell us whether or not to rebase a merge,
>   respect CLI and ignore config.
>
> What mode should we use?
>
> - If neither CLI or config tells us what mode to use, default to
>   "no-rebase-cousins" (or whatever default we decide).
> - If one of CLI or config tells us what mode to use, respect it.
> - If both CLI or config tell us what mode to use, respect CLI and ignore
>   config.
>
> If users cleanly separate the two concepts, I think it is quite clear.
> (I'm not advocating for this approach, but) e.g. if we pretend that each
> behavior were configured separately, like:
>
> --[no-]rebase-merges [--rebase-merges-mode=(rebase-cousins|no-rebase-cousins)]
>
> I don't think there would be any confusion. (Having --rebase-merges-mode
> be a no-op without --rebase-merges is probably even more confusing to
> users, plus this would break backwards compatibility, so I don't think
> this is a good idea at all.)
>
> Your doc patch explains the rules pretty clearly, but perhaps it doesn't
> explain this mental model clearly enough, hence the confusion. If we
> don't find a good way to communicate this (I think it is clear, but
> other reviewers seem yet unconvinced), I wouldn't mind taking Phillip's
> suggestion to have "--rebase-merges" override
> "rebase.rebaseMerges='specific-mode'".

I got the impression that everyone, including Phillip,[1] already agrees that the proposed documentation is clear about the interaction between the config option and the command line option. However, it is a little weird when you consider that other flags with optional arguments, like `git branch --track`, unconditionally override their corresponding config options.[2]

Let me ask a different but related question: If we add a rebase-evil-merges mode, do you think that would be orthogonal to the rebase-cousins mode?

-Alex

[1] https://lore.kernel.org/git/7cf19017-518b-245e-aea2-5dee55f88276@dunelm.org.uk/ [2] https://lore.kernel.org/git/5551d67b-3021-8cfc-53b5-318f223ded6d@dunelm.org.uk/

Glen Choo· Mar 16, 2023, 22:39 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 13 quoted lines
>> Your doc patch explains the rules pretty clearly, but perhaps it doesn't
>> explain this mental model clearly enough, hence the confusion. If we
>> don't find a good way to communicate this (I think it is clear, but
>> other reviewers seem yet unconvinced), I wouldn't mind taking Phillip's
>> suggestion to have "--rebase-merges" override
>> "rebase.rebaseMerges='specific-mode'".
>
> I got the impression that everyone, including Phillip,[1] already
> agrees that the proposed documentation is clear about the interaction
> between the config option and the command line option. However, it is
> a little weird when you consider that other flags with optional
> arguments, like `git branch --track`, unconditionally override their
> corresponding config options.[2]

Ah, I didn't consider other options like `git branch --track`. I haven't looked into what is the norm, but I think we should follow it (whatever it is).

If other reviewers have a strong idea of what this norm is, I am happy to defer to them. If not, I can look into it given some time.

> Let me ask a different but related question: If we add a
> rebase-evil-merges mode, do you think that would be orthogonal to the
> rebase-cousins mode?

I am not an expert on this, so perhaps my opinion isn't that important ;)

My understanding is that `[no-]rebase-cousins` affects which commits get rebased and onto where, whereas `rebase-evil-merges` would affect how the merge commits are generated (by rebasing the evil or by simply recreating the merges). From that perspective, it seems like yes, the two would be orthogonal.

Hm. Maybe this means that we'd be introducing a new option, and that my hunch that we would change the default to `rebase-evil-merges` is more wrong than I expected.

Though I guess this doesn't really affects what we do with the CLI options _now_, since the discussion is about what we do about defaults, and what the default is is quite immaterial.

Show 5 quoted lines
>
> -Alex
>
> [1] https://lore.kernel.org/git/7cf19017-518b-245e-aea2-5dee55f88276@dunelm.org.uk/
> [2] https://lore.kernel.org/git/5551d67b-3021-8cfc-53b5-318f223ded6d@dunelm.org.uk/
Alex Henrie· Mar 18, 2023, 05:59 UTC · re: Glen Choo · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 16, 2023 at 4:39 PM Glen Choo <chooglen@google.com> wrote:
Show 44 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> >> Your doc patch explains the rules pretty clearly, but perhaps it doesn't
> >> explain this mental model clearly enough, hence the confusion. If we
> >> don't find a good way to communicate this (I think it is clear, but
> >> other reviewers seem yet unconvinced), I wouldn't mind taking Phillip's
> >> suggestion to have "--rebase-merges" override
> >> "rebase.rebaseMerges='specific-mode'".
> >
> > I got the impression that everyone, including Phillip,[1] already
> > agrees that the proposed documentation is clear about the interaction
> > between the config option and the command line option. However, it is
> > a little weird when you consider that other flags with optional
> > arguments, like `git branch --track`, unconditionally override their
> > corresponding config options.[2]
>
> Ah, I didn't consider other options like `git branch --track`. I haven't
> looked into what is the norm, but I think we should follow it (whatever
> it is).
>
> If other reviewers have a strong idea of what this norm is, I am happy
> to defer to them. If not, I can look into it given some time.
>
> > Let me ask a different but related question: If we add a
> > rebase-evil-merges mode, do you think that would be orthogonal to the
> > rebase-cousins mode?
>
> I am not an expert on this, so perhaps my opinion isn't that important
> ;)
>
> My understanding is that `[no-]rebase-cousins` affects which commits get
> rebased and onto where, whereas `rebase-evil-merges` would affect how
> the merge commits are generated (by rebasing the evil or by simply
> recreating the merges). From that perspective, it seems like yes, the
> two would be orthogonal.
>
> Hm. Maybe this means that we'd be introducing a new option, and that my
> hunch that we would change the default to `rebase-evil-merges` is more
> wrong than I expected.
>
> Though I guess this doesn't really affects what we do with the CLI
> options _now_, since the discussion is about what we do about defaults,
> and what the default is is quite immaterial.

I looked through the code and documentation and found 12 good examples of command line flags with an optional argument that always override their corresponding config options whether or not the optional argument is given:

git -c branch.autoSetupMerge=inherit branch --track mynewbranch
git -c commit.gpgSign=mykey commit --gpg-sign
git -c core.sharedRepository=0666 init --shared .
git -c diff.ignoreSubmodules=untracked diff --ignore-submodules
git -c diff.submodule=diff diff --submodule
git -c format.attach=myboundary format-patch --attach HEAD^
git -c format.from='Dang Git <dang@example.org>' format-patch --from HEAD^
git -c format.thread=deep format-patch --thread HEAD~3
git -c gc.pruneExpire=1.week.ago gc --prune
git -c log.decorate=full log --decorate
git -c merge.log=1 merge --log mytopicbranch
git -c pull.rebase=interactive pull --rebase

I found 6 other similar examples where the config option is a yes/no/auto trilean, but I don't think those are good examples because it makes total sense for a command line flag without an argument to override a nonspecific "auto" value in the configuration:

git -c color.diff=auto diff --color | less
git -c color.grep=auto grep --color . | less
git -c color.showbranch=auto show-branch --color master mytopicbranch | less
git -c color.ui=auto log --color | less
git -c fetch.recurseSubmodules=on-demand fetch --recurse-submodules
git -c push.gpgSign=if-asked push --signed

I found 1 good example where the config option does make a difference if the optional argument is omitted:

git -c diff.colorMoved=plain diff --color-moved

And I found 1 other similar example, but it's not good a example because the config option doesn't do anything unless the command line flag is specified:

git -c diff.dirstat=lines diff --dirstat

Given 12 good examples versus 1 good counterexample, I would say that the established convention is for the command line flag to always override the config option. Please let me know if there are other examples that I missed.

It's worth noting that the documentation for `git format-patch --thread` explicitly says that format.thread takes precedence over --thread without an argument, but that is not correct: When I tested it, the command line argument always took precedence.

Another problem is that the documentation for `git pack-objects` does not even mention that --unpack-unreachable has an optional argument, and it says that the argument to --cruft-expiration is required when it is not.

I'm not going to fix all the documentation problems, at least not right now. However, in order to follow the established convention for flags with optional arguments, and because we probably aren't going to use rebase.rebaseMerges to facilitate a future change of defaults, I will redo the patch so that --rebase-merges without an argument takes precedence over rebase.rebaseMerges.

Thanks for the feedback. I feel more confident about the way forward now.
-Alex
Johannes Schindelin· Mar 24, 2023, 15:05 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Hi Alex,
On Thu, 16 Mar 2023, Alex Henrie wrote:
> If we add a rebase-evil-merges mode, do you think that would be
> orthogonal to the rebase-cousins mode?
Those two concepts are very much orthogonal.

The rebase-evil-merges mode needs to change the way the `merge` command operates, from "forget everything but the commit message of the original merge commit" to "do try to figure out what amendments were made to the original merge and replay them".

Whether cousins are rebased or not has everything to do with the `reset` command, though.

Elsewhere in the thread, you mentioned a suspicion that `rebase-cousins` should become the default. However, that would be a radical shift from the spirit of the `git rebase [-r] <onto>` command: If `<onto>` is reachable from `HEAD`, the idea is that an unchanged rebase script will produce the identical commit topology. This is in line with a regular (non-`-r`) rebase as long as there are no merge commits between `<onto>` and `HEAD`. I am not sure that such a radical shift could ever be an easy sell.

Ciao, Johannes

Sergey Organov· Mar 25, 2023, 16:59 UTC · re: Alex Henrie · lore

Re: [PATCH v6 3/3] rebase: add a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
[...]
> Let me ask a different but related question: If we add a
> rebase-evil-merges mode, do you think that would be orthogonal to the
> rebase-cousins mode?

This is the question that I considered as well. To me they look orthogonal, and I'm afraid we will have hard times extending --rebase-merges gracefully in a backward-compatible manner.

The set:

--rebase-merges=remerge --rebase-merges=rebase --rebase-merges=off

looks natural to me, but "cousins" that affect what is to be rebased (as opposed to how, if at all), will apparently get in the way. Besides, I recently ran into similar problem with --diff-merges (when|how) dichotomy, and there was no elegant solution found, even though a working one has been implemented, and then there were some discussions around.

Also, I'm not quite sure that these "cousins" options make no sense when we in fact flatten history (--no-rebase-merges case). To me it looks like there should have been separate --[no-]rebase-cousins option, provided we do need this choice in the first place, but I definitely might be wrong here as well.

As a side note, it sounds both unfair and confusing to use "rebase-evil-merges" term to describe a mode that will actually rebase (all the) merges, as neither a merge needs to be 'evil' to be the object of rebase operation, nor dedicated mode is necessarily needed to rebase specifically 'evil' merges (whatever they actually are.)

Thanks, -- Sergey Organov

Alex Henrie· Mar 5, 2023, 05:07 UTC · re: Alex Henrie · lore

[PATCH v6 1/3] rebase: add documentation and test for --no-rebase-merges

As far as I can tell, --no-rebase-merges has always worked, but has never been documented. It is especially important to document it before a rebase.rebaseMerges option is introduced so that users know how to override the config option on the command line. It's also important to clarify that --rebase-merges without an argument is not the same as --no-rebase-merges and not passing --rebase-merges is not the same as passing --rebase-merges=no-rebase-cousins.

A test case is necessary to make sure that --no-rebase-merges keeps working after its code is refactored in the following patches of this series. The test case is a little contrived: It's unlikely that a user would type both --rebase-merges and --no-rebase-merges at the same time. However, if an alias is defined which includes --rebase-merges, the user might decide to add --no-rebase-merges to countermand that part of the alias but leave alone other flags set by the alias.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt | 18 +++++++++++-------
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 21 insertions(+), 7 deletions(-)
Show changes to 2 files +21 −7

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..4e57a87624 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,20 +529,24 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
-By default, or when `no-rebase-cousins` was specified, commits which do not
-have `<upstream>` as direct ancestor will keep their original branch point,
-i.e. commits that would be excluded by linkgit:git-log[1]'s
-`--ancestry-path` option will keep their original ancestry by default. If
-the `rebase-cousins` mode is turned on, such commits are instead rebased
-onto `<upstream>` (or `<onto>`, if specified).
+When rebasing merges, there are two modes: `rebase-cousins` and
+`no-rebase-cousins`. If the mode is not specified, it defaults to
+`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
+`<upstream>` as direct ancestor will keep their original branch point, i.e.
+commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
+option will keep their original ancestry by default. In `rebase-cousins` mode,
+such commits are instead rebased onto `<upstream>` (or `<onto>`, if
+specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.39.2
Sergey Organov· Mar 8, 2023, 22:25 UTC · re: Alex Henrie · lore

Re: [PATCH v6 1/3] rebase: add documentation and test for --no-rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
[...]
> +When rebasing merges, there are two modes: `rebase-cousins` and
> +`no-rebase-cousins`. If the mode is not specified, it defaults to
> +`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
> +`<upstream>` as direct ancestor will keep their original branch point, i.e.

I realize this is in fact unchanged from the original, so is not exactly material of these series, but what is the meaning of "direct ancestor"? Is it just "parent"?

> +commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
> +option will keep their original ancestry by default.

Excluded when --ancestry-path is applied to what commit, exactly? To the commit being considered for rebase, or to the <upstream>, or to the 'fork_point'?

Please notice that rebase claims to operate either on <upstream>..HEAD or 'fork_point'..HEAD range, and --ancestry-path without arguments applies to the left commit of the range when used in "git log".

Looks like some clarifications are needed here, even though maybe not in these series?

Thanks, -- Sergey Organov

Alex Henrie· Mar 5, 2023, 05:07 UTC · re: Alex Henrie · lore

[PATCH v6 2/3] rebase: deprecate --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges without an argument. Deprecate that syntax to avoid confusion when a rebase.rebaseMerges config option is introduced, where rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.

It is not likely that anyone is actually using this syntax, but just in case, deprecate the empty string argument instead of dropping support for it immediately.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +6 −2
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6635f10d52..c36ddc0050 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1438,7 +1438,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (rebase_merges) {
 		if (!*rebase_merges)
-			; /* default mode; do nothing */
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
 		else if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
-- 
2.39.2
Phillip Wood· Mar 7, 2023, 14:59 UTC · re: Alex Henrie · lore

Re: [PATCH v6 2/3] rebase: deprecate --rebase-merges=""

Hi Alex
On 05/03/2023 05:07, Alex Henrie wrote:
Show 10 quoted lines
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 6635f10d52..c36ddc0050 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
>   			N_("mode"),
>   			N_("try to rebase merges instead of skipping them"),
> -			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
> +			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},

I've just realized there is a subtle behavior change here. If the user passes "--rebase-merges=rebase-cousins --rebase-merges" we used to rebase cousins but now we wont. The next patch goes back to the old behavior so I don't think we need to worry about it.

Best Wishes
Phillip
Show 16 quoted lines
>   		OPT_BOOL(0, "fork-point", &options.fork_point,
>   			 N_("use 'merge-base --fork-point' to refine upstream")),
>   		OPT_STRING('s', "strategy", &options.strategy,
> @@ -1438,7 +1438,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   
>   	if (rebase_merges) {
>   		if (!*rebase_merges)
> -			; /* default mode; do nothing */
> +			warning(_("--rebase-merges with an empty string "
> +				  "argument is deprecated and will stop "
> +				  "working in a future version of Git. Use "
> +				  "--rebase-merges without an argument "
> +				  "instead, which does the same thing."));
>   		else if (!strcmp("rebase-cousins", rebase_merges))
>   			options.rebase_cousins = 1;
>   		else if (strcmp("no-rebase-cousins", rebase_merges))
Sergey Organov· Mar 5, 2023, 12:22 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
[...]
> - Rephrase the warning about --rebase-merges="" to recommend
>   --rebase-merges without an argument instead, and clarify that that
>   will do the same thing

Not a strong objection to the series as they are, but, provided options with optional arguments are considered to be a bad practice in general (unless something had recently changed), I'd like to vote for adding:

  --rebase-merges=on (≡ --reabase-merges="")

and for suggesting to use that one instead of --rebase-merges="" rather than naked --rebase-merges.

It'd be best to actually fix --rebase-merges to always have an argument, the more so as we have '-r' shortcut, but as this is unlikely to fly due to backward compatibility considerations, this way we would at least avoid promoting bad practices further.

If accepted, please consider to introduce
  --rebase-merges=off (≡ --no-rebase-merges)
as well for completeness.

BTW, that's how we settled upon in the implementation of --diff-merges, so these two will then be mutually consistent.

Thanks, -- Sergey Organov

Alex Henrie· Mar 5, 2023, 21:33 UTC · re: Sergey Organov · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

On Sun, Mar 5, 2023 at 5:22 AM Sergey Organov <sorganov@gmail.com> wrote:
Show 31 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> [...]
>
> > - Rephrase the warning about --rebase-merges="" to recommend
> >   --rebase-merges without an argument instead, and clarify that that
> >   will do the same thing
>
> Not a strong objection to the series as they are, but, provided options
> with optional arguments are considered to be a bad practice in general
> (unless something had recently changed), I'd like to vote for adding:
>
>   --rebase-merges=on (≡ --reabase-merges="")
>
> and for suggesting to use that one instead of --rebase-merges="" rather
> than naked --rebase-merges.
>
> It'd be best to actually fix --rebase-merges to always have an argument,
> the more so as we have '-r' shortcut, but as this is unlikely to fly due
> to backward compatibility considerations, this way we would at least
> avoid promoting bad practices further.
>
> If accepted, please consider to introduce
>
>   --rebase-merges=off (≡ --no-rebase-merges)
>
> as well for completeness.
>
> BTW, that's how we settled upon in the implementation of --diff-merges,
> so these two will then be mutually consistent.

I am curious as to why you say that flags with optional arguments are considered bad practice. I don't see an advantage to adding --rebase-merges=on and then telling users that they ought to always type the extra three characters, even if we were designing the feature from scratch. As it is, we're certainly not going to drop support for --no-rebase-merges or --rebase-merges without an argument, so it seems fine to advertise them to users. And if we do want to add support for passing a boolean value as an argument to --rebase-merges, that can be done after the rebase.rebaseMerges config option is added; it's not a prerequisite.

-Alex
Sergey Organov· Mar 5, 2023, 22:54 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 35 quoted lines
> On Sun, Mar 5, 2023 at 5:22 AM Sergey Organov <sorganov@gmail.com> wrote:
>>
>> Alex Henrie <alexhenrie24@gmail.com> writes:
>>
>> [...]
>>
>> > - Rephrase the warning about --rebase-merges="" to recommend
>> >   --rebase-merges without an argument instead, and clarify that that
>> >   will do the same thing
>>
>> Not a strong objection to the series as they are, but, provided options
>> with optional arguments are considered to be a bad practice in general
>> (unless something had recently changed), I'd like to vote for adding:
>>
>>   --rebase-merges=on (≡ --reabase-merges="")
>>
>> and for suggesting to use that one instead of --rebase-merges="" rather
>> than naked --rebase-merges.
>>
>> It'd be best to actually fix --rebase-merges to always have an argument,
>> the more so as we have '-r' shortcut, but as this is unlikely to fly due
>> to backward compatibility considerations, this way we would at least
>> avoid promoting bad practices further.
>>
>> If accepted, please consider to introduce
>>
>>   --rebase-merges=off (≡ --no-rebase-merges)
>>
>> as well for completeness.
>>
>> BTW, that's how we settled upon in the implementation of --diff-merges,
>> so these two will then be mutually consistent.
>
> I am curious as to why you say that flags with optional arguments are
> considered bad practice.

As far as I can tell, the core problem with such options is that generic options parsing code can't tell if in "--option value" "value" is an argument to "--option", or separate argument, that in turn may lead to very surprising behaviors and bugs.

I believe it's a common knowledge here. If I'm wrong, then the rest simply doesn't make sense.

> I don't see an advantage to adding --rebase-merges=on and then telling
> users that they ought to always type the extra three characters, even
> if we were designing the feature from scratch.

The advantage is that we avoid optional arguments. That said, there is '-r' that is 13 characters shorter than --rebase-merges, so another option is to advertise that, provided you are still opposed to "--rebase-merges=on".

> As it is, we're certainly not going to drop support for
> --no-rebase-merges
--no-rebase-merges is fine, as it has no optional arguments
> or --rebase-merges without an argument,

Yep, but that's a pity and the whole point of my comment: if we can't fix it, at least don't promote it.

> so it seems fine to advertise them to users.
--no-rebase-merges is fine, but then you don't advertise it anyway.
> And if we do want to add support for passing a boolean value as an
> argument to --rebase-merges, that can be done after the
> rebase.rebaseMerges config option is added; it's not a prerequisite.

Yep, that's why I said at the beginning that this is not an objection to the series as they are, rather a nitpick.

Thanks, -- Sergey

Alex Henrie· Mar 6, 2023, 00:02 UTC · re: Sergey Organov · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

On Sun, Mar 5, 2023 at 3:54 PM Sergey Organov <sorganov@gmail.com> wrote:
Show 43 quoted lines
>
> Alex Henrie <alexhenrie24@gmail.com> writes:
>
> > On Sun, Mar 5, 2023 at 5:22 AM Sergey Organov <sorganov@gmail.com> wrote:
> >>
> >> Alex Henrie <alexhenrie24@gmail.com> writes:
> >>
> >> [...]
> >>
> >> > - Rephrase the warning about --rebase-merges="" to recommend
> >> >   --rebase-merges without an argument instead, and clarify that that
> >> >   will do the same thing
> >>
> >> Not a strong objection to the series as they are, but, provided options
> >> with optional arguments are considered to be a bad practice in general
> >> (unless something had recently changed), I'd like to vote for adding:
> >>
> >>   --rebase-merges=on (≡ --reabase-merges="")
> >>
> >> and for suggesting to use that one instead of --rebase-merges="" rather
> >> than naked --rebase-merges.
> >>
> >> It'd be best to actually fix --rebase-merges to always have an argument,
> >> the more so as we have '-r' shortcut, but as this is unlikely to fly due
> >> to backward compatibility considerations, this way we would at least
> >> avoid promoting bad practices further.
> >>
> >> If accepted, please consider to introduce
> >>
> >>   --rebase-merges=off (≡ --no-rebase-merges)
> >>
> >> as well for completeness.
> >>
> >> BTW, that's how we settled upon in the implementation of --diff-merges,
> >> so these two will then be mutually consistent.
> >
> > I am curious as to why you say that flags with optional arguments are
> > considered bad practice.
>
> As far as I can tell, the core problem with such options is that generic
> options parsing code can't tell if in "--option value" "value" is an
> argument to "--option", or separate argument, that in turn may lead to
> very surprising behaviors and bugs.

Interesting, thank you for clarifying. I just tried it and it appears that --rebase-merges requires an equals sign when it has an argument. For example:

$ git rebase --rebase-merges=rebase-cousins Current branch master is up to date.

$ git rebase --rebase-merges rebase-cousins fatal: invalid upstream 'rebase-cousins'

So there is no ambiguity because if an argument to a flag is optional, an equals sign is required.

Conversely, because the argument to --diff-merges is required, the equals sign is optional.

I can see the argument (no pun intended) for avoiding optional arguments to flags because it is confusing that '=' is required for optional arguments but not for mandatory arguments, and it's easy to forget which arguments are mandatory and which are optional.

However, --rebase-merges already has an optional argument. If we make it look more like --diff-merges by accepting the value "on", it could actually be even more confusing because users would be more likely to try "--rebase-merges on", thinking that that will work because "--diff-merges on" works.

Given the current situation, users are just going to have to learn that in Git, optional arguments to flags must be preceded by an equals sign. Maybe someday Git will make breaking changes to get rid of optional arguments to flags, but for better or for worse, that day will not come soon :-/

Show 13 quoted lines
> > As it is, we're certainly not going to drop support for
> > --no-rebase-merges
>
> --no-rebase-merges is fine, as it has no optional arguments
>
> > or --rebase-merges without an argument,
>
> Yep, but that's a pity and the whole point of my comment: if we can't
> fix it, at least don't promote it.
>
> > so it seems fine to advertise them to users.
>
> --no-rebase-merges is fine, but then you don't advertise it anyway.

I am not sure what you mean by this. The first patch of the series adds documentation and a test for --no-rebase-merges, so I am advertising it. Or are you saying that I /should/ advertise neither --no-rebase-merges nor --rebase-merges without an argument, because you think --rebase-merges=off and --rebase-merges=on would be better?

-Alex
Sergey Organov· Mar 6, 2023, 13:23 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
[...]
Show 7 quoted lines
>> > so it seems fine to advertise them to users.
>>
>> --no-rebase-merges is fine, but then you don't advertise it anyway.
>
> I am not sure what you mean by this. The first patch of the series
> adds documentation and a test for --no-rebase-merges, so I am
> advertising it.
Ah, yes, you do it there, and that's fine with me.

I meant only the part where you suggest --rebase-merges instead of --rebase-merges="". I have no nitpicks about any other parts of the series.

> Or are you saying that I /should/ advertise neither --no-rebase-merges
> nor --rebase-merges without an argument, because you think
> --rebase-merges=off and --rebase-merges=on would be better?
No, --no-rebase-merges is fine as far as I'm concerned.

Thanks, -- Sergey

Junio C Hamano· Mar 6, 2023, 19:08 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 12 quoted lines
> Interesting, thank you for clarifying. I just tried it and it appears
> that --rebase-merges requires an equals sign when it has an argument.
> For example:
>
> $ git rebase --rebase-merges=rebase-cousins
> Current branch master is up to date.
>
> $ git rebase --rebase-merges rebase-cousins
> fatal: invalid upstream 'rebase-cousins'
>
> So there is no ambiguity because if an argument to a flag is optional,
> an equals sign is required.
Exactly.

It is not a good excuse that users can express something unambiguously to the machinery once they know they need to use '=', when they are so accustomed to giving values to ordinary options without. This is why options with optional value is considered a bad UI element, because the way "--opt val" is interpreted for them is different from everybody else. And it burdens the users by forcing them to _know_ which ones are with optional value.

Since it is an existing UI breakage, as long as the series is not making it worse or harder to fix in the future, it is fine, though.

Junio C Hamano· Mar 6, 2023, 17:19 UTC · re: Sergey Organov · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Sergey Organov <sorganov@gmail.com> writes:
Show 7 quoted lines
>> I am curious as to why you say that flags with optional arguments are
>> considered bad practice.
>
> As far as I can tell, the core problem with such options is that generic
> options parsing code can't tell if in "--option value" "value" is an
> argument to "--option", or separate argument, that in turn may lead to
> very surprising behaviors and bugs.
Another one and half reasons are
 * Can there be two or more such options used on a single command
   line?  Unless we limit the command line and say "such an option
   can appear only at the end of options and without any non option
   arguments" (the latter is what you said above), we'd end up with
   a system that cannot be explained to casual users.
 * What about short-form of option?  Is "-abc" asking for three
   options "-a", "-b", and "-c"?  Or is it "-a" option that takes
   optional argument "bc"?

There are some advices we give in "git help cli" to our users, which we shouldn't have to have given if we rejected flags with optional arguments in our commands.

Thanks.
    
Phillip Wood· Mar 6, 2023, 16:24 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Hi Alex
On 05/03/2023 05:07, Alex Henrie wrote:
Show 33 quoted lines
> This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and
> documents the behavior of the --rebase-merges command line option to
> avoid confusion about how the config option and the command line option
> interact.
> 
> Changes from v5:
> - Add commit message note about --no-rebase-merges having always worked
> - Add commit message note about the test for --no-rebase-merges being
>    somewhat contrived
> - Rephrase the documentation to avoid using the phrase "By default" with
>    two different meanings, and in so doing clarify that --rebase-merges
>    without an argument is not the same as --no-rebase-merges and not
>    passing --rebase-merges is not the same as passing
>    --rebase-merges=no-rebase-cousins
> - Add commit message note about keeping --rebase-merges="" for now out
>    of an abundance of caution
> - Rephrase the warning about --rebase-merges="" to recommend
>    --rebase-merges without an argument instead, and clarify that that
>    will do the same thing
> - Remove the test for --rebase-merges=""
> - Rename the config option from rebase.merges to rebase.rebaseMerges and
>    explain why in the commit message
> - Add commit message note about why "true" is a valid option for
>    rebase.rebaseMerges and why --rebase-merges without an argument does
>    not clobber the mode specified in rebase.rebaseMerges
> - Rephrase the documentation to clarify that --rebase-merges without an
>    argument does not clobber the mode specified in rebase.rebaseMerges
> - Add another test for incompatible options
> 
> Suggestions on v5 not incorporated in v6:

Thanks for adding this, it is really helpful to see what did not change as well as what did change. It is also helpful to briefly explain why you disagree with the suggestions so others can understand why you decided not to make these changes.

> - Make --rebase-merges without an argument clobber the mode specified in
>    rebase.rebaseMerges

I'm still confused as to why we want the config value to change the behavior of --rebase-merges. Is it for "git pull --rebase=merges"? If that's the case then I think a better approach is for pull to parse rebase.merges and pass the appropriate argument to rebase. That way we don't break the expectation that command line arguments override config values.

> - Remove the test for --rebase-merges=no-rebase-cousins overriding
>    rebase.rebaseMerges=rebase-cousins
> - In the tests, check the graph itself instead of checking that the
>    graph has not changed by checking that the commit hash has not changed

I'm not sure what value the existing test has if it only checks that HEAD is unchanged after the rebase. It could be unchanged because the rebase fast-forwarded or because it did nothing.

Best Wishes
Phillip
Show 338 quoted lines
> Thanks to Glen, Calvin, Junio, Jonathan, Elijah, and Phillip for your
> feedback on v5. As before, if you feel strongly about one of the
> suggestions that I have not incorporated into v6, or if you see
> something else amiss, let's continue the discussion.
> 
> Alex Henrie (3):
>    rebase: add documentation and test for --no-rebase-merges
>    rebase: deprecate --rebase-merges=""
>    rebase: add a config option for --rebase-merges
> 
>   Documentation/config/rebase.txt        | 11 ++++
>   Documentation/git-rebase.txt           | 19 ++++---
>   builtin/rebase.c                       | 75 ++++++++++++++++++-------
>   t/t3422-rebase-incompatible-options.sh | 17 ++++++
>   t/t3430-rebase-merges.sh               | 78 ++++++++++++++++++++++++++
>   5 files changed, 174 insertions(+), 26 deletions(-)
> 
> Range-diff against v5:
> 1:  76e38ef9f8 ! 1:  bf08c03ba7 rebase: add documentation and test for --no-rebase-merges
>      @@ Metadata
>        ## Commit message ##
>           rebase: add documentation and test for --no-rebase-merges
>       
>      +    As far as I can tell, --no-rebase-merges has always worked, but has
>      +    never been documented. It is especially important to document it before
>      +    a rebase.rebaseMerges option is introduced so that users know how to
>      +    override the config option on the command line. It's also important to
>      +    clarify that --rebase-merges without an argument is not the same as
>      +    --no-rebase-merges and not passing --rebase-merges is not the same as
>      +    passing --rebase-merges=no-rebase-cousins.
>      +
>      +    A test case is necessary to make sure that --no-rebase-merges keeps
>      +    working after its code is refactored in the following patches of this
>      +    series. The test case is a little contrived: It's unlikely that a user
>      +    would type both --rebase-merges and --no-rebase-merges at the same time.
>      +    However, if an alias is defined which includes --rebase-merges, the user
>      +    might decide to add --no-rebase-merges to countermand that part of the
>      +    alias but leave alone other flags set by the alias.
>      +
>           Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
>       
>        ## Documentation/git-rebase.txt ##
>      @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
>       +	resolved/re-applied manually. `--no-rebase-merges` can be used to
>       +	countermand a previous `--rebase-merges`.
>        +
>      - By default, or when `no-rebase-cousins` was specified, commits which do not
>      - have `<upstream>` as direct ancestor will keep their original branch point,
>      +-By default, or when `no-rebase-cousins` was specified, commits which do not
>      +-have `<upstream>` as direct ancestor will keep their original branch point,
>      +-i.e. commits that would be excluded by linkgit:git-log[1]'s
>      +-`--ancestry-path` option will keep their original ancestry by default. If
>      +-the `rebase-cousins` mode is turned on, such commits are instead rebased
>      +-onto `<upstream>` (or `<onto>`, if specified).
>      ++When rebasing merges, there are two modes: `rebase-cousins` and
>      ++`no-rebase-cousins`. If the mode is not specified, it defaults to
>      ++`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
>      ++`<upstream>` as direct ancestor will keep their original branch point, i.e.
>      ++commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
>      ++option will keep their original ancestry by default. In `rebase-cousins` mode,
>      ++such commits are instead rebased onto `<upstream>` (or `<onto>`, if
>      ++specified).
>      + +
>      + It is currently only possible to recreate the merge commits using the
>      + `ort` merge strategy; different merge strategies can be used only via
>       
>        ## t/t3430-rebase-merges.sh ##
>       @@ t/t3430-rebase-merges.sh: test_expect_success 'with a branch tip that was cherry-picked already' '
> 2:  c6099e6dee ! 2:  26f98b8400 rebase: deprecate --rebase-merges=""
>      @@ Commit message
>       
>           The unusual syntax --rebase-merges="" (that is, --rebase-merges with an
>           empty string argument) has been an undocumented synonym of
>      -    --rebase-merges=no-rebase-cousins. Deprecate that syntax to avoid
>      -    confusion when a rebase.merges config option is introduced, where
>      -    rebase.merges="" will be equivalent to --no-rebase-merges.
>      +    --rebase-merges without an argument. Deprecate that syntax to avoid
>      +    confusion when a rebase.rebaseMerges config option is introduced, where
>      +    rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.
>      +
>      +    It is not likely that anyone is actually using this syntax, but just in
>      +    case, deprecate the empty string argument instead of dropping support
>      +    for it immediately.
>       
>           Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
>       
>      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
>       +			warning(_("--rebase-merges with an empty string "
>       +				  "argument is deprecated and will stop "
>       +				  "working in a future version of Git. Use "
>      -+				  "--rebase-merges=no-rebase-cousins "
>      -+				  "instead."));
>      ++				  "--rebase-merges without an argument "
>      ++				  "instead, which does the same thing."));
>        		else if (!strcmp("rebase-cousins", rebase_merges))
>        			options.rebase_cousins = 1;
>        		else if (strcmp("no-rebase-cousins", rebase_merges))
>      -
>      - ## t/t3430-rebase-merges.sh ##
>      -@@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless asked for' '
>      - 	EOF
>      - '
>      -
>      -+test_expect_success '--rebase-merges="" is deprecated' '
>      -+	git rebase --rebase-merges="" HEAD^ 2>actual &&
>      -+	grep deprecated actual
>      -+'
>      -+
>      - test_expect_success 'refs/rewritten/* is worktree-local' '
>      - 	git worktree add wt &&
>      - 	cat >wt/script-from-scratch <<-\EOF &&
> 3:  95cba9588c ! 3:  402365256c rebase: add a config option for --rebase-merges
>      @@ Commit message
>           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 possibly turning
>      -    on --rebase-merges by default without configuration in a future version
>      -    of Git.
>      +    --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.
>      +
>      +    In the future, the default rebase-merges mode may change from
>      +    no-rebase-cousins to rebase-cousins. Support setting rebase.rebaseMerges
>      +    to the nonspecific value "true" for users who do not need or want to
>      +    care about the default changing in the future. Similarly, for users who
>      +    have --rebase-merges in an alias and want to get the future behavior
>      +    now, use the specific rebase-merges mode from the config if a specific
>      +    mode is not given on the command line.
>       
>           Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
>       
>      @@ Documentation/config/rebase.txt: rebase.rescheduleFailedExec::
>        rebase.forkPoint::
>        	If set to false set `--no-fork-point` option by default.
>       +
>      -+rebase.merges::
>      ++rebase.rebaseMerges::
>       +	Whether and how to set the `--rebase-merges` option by default. Can
>       +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
>       +	true is equivalent to `--rebase-merges` without an argument, setting to
>       +	`rebase-cousins` or `no-rebase-cousins` is equivalent to
>       +	`--rebase-merges` with that value as its argument, and setting to false
>       +	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
>      -+	command line without an argument overrides a `rebase.merges=false`
>      -+	configuration but does not override other values of `rebase.merge`.
>      ++	command line without an argument overrides a `rebase.rebaseMerges=false`
>      ++	configuration, but the absence of a specific rebase-merges mode on the
>      ++	command line does not counteract a specific mode set in the configuration.
>       
>        ## Documentation/git-rebase.txt ##
>       @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
>      @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
>        	manual amendments in these merge commits will have to be
>        	resolved/re-applied manually. `--no-rebase-merges` can be used to
>       -	countermand a previous `--rebase-merges`.
>      -+	countermand both the `rebase.merges` config option and a previous
>      ++	countermand both the `rebase.rebaseMerges` config option and a previous
>       +	`--rebase-merges`.
>        +
>      - By default, or when `no-rebase-cousins` was specified, commits which do not
>      - have `<upstream>` as direct ancestor will keep their original branch point,
>      + When rebasing merges, there are two modes: `rebase-cousins` and
>      +-`no-rebase-cousins`. If the mode is not specified, it defaults to
>      +-`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
>      +-`<upstream>` as direct ancestor will keep their original branch point, i.e.
>      +-commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
>      +-option will keep their original ancestry by default. In `rebase-cousins` mode,
>      +-such commits are instead rebased onto `<upstream>` (or `<onto>`, if
>      +-specified).
>      ++`no-rebase-cousins`. If the mode is not specified on the command line or in
>      ++the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
>      ++In `no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
>      ++ancestor will keep their original branch point, i.e. commits that would be
>      ++excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
>      ++original ancestry by default. In `rebase-cousins` mode, such commits are
>      ++instead rebased onto `<upstream>` (or `<onto>`, if specified).
>      + +
>      + It is currently only possible to recreate the merge commits using the
>      + `ort` merge strategy; different merge strategies can be used only via
>       
>        ## builtin/rebase.c ##
>       @@ builtin/rebase.c: struct rebase_options {
>      @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
>        		return 0;
>        	}
>        
>      -+	if (!strcmp(var, "rebase.merges")) {
>      ++	if (!strcmp(var, "rebase.rebasemerges")) {
>       +		opts->config_rebase_merges = git_parse_maybe_bool(value);
>       +		if (opts->config_rebase_merges < 0) {
>       +			opts->config_rebase_merges = 1;
>      @@ builtin/rebase.c: static int parse_opt_empty(const struct option *opt, const cha
>       +			warning(_("--rebase-merges with an empty string "
>       +				  "argument is deprecated and will stop "
>       +				  "working in a future version of Git. Use "
>      -+				  "--rebase-merges=no-rebase-cousins "
>      -+				  "instead."));
>      -+			arg = "no-rebase-cousins";
>      ++				  "--rebase-merges without an argument "
>      ++				  "instead, which does the same thing."));
>      ++			return 0;
>       +		}
>       +		parse_rebase_merges_value(options, arg);
>       +	}
>      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
>       -			warning(_("--rebase-merges with an empty string "
>       -				  "argument is deprecated and will stop "
>       -				  "working in a future version of Git. Use "
>      --				  "--rebase-merges=no-rebase-cousins "
>      --				  "instead."));
>      +-				  "--rebase-merges without an argument "
>      +-				  "instead, which does the same thing."));
>       -		else if (!strcmp("rebase-cousins", rebase_merges))
>       -			options.rebase_cousins = 1;
>       -		else if (strcmp("no-rebase-cousins", rebase_merges))
>      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
>       +			if (options.autosquash == -1 && options.config_autosquash == 1)
>        				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
>       +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
>      -+				die(_("apply options are incompatible with rebase.merges.  Consider adding --no-rebase-merges"));
>      ++				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>        			else if (options.update_refs == -1 && options.config_update_refs == 1)
>        				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
>       +			else if (is_merge(&options))
>      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
>        	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
>       
>        ## t/t3422-rebase-incompatible-options.sh ##
>      +@@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
>      + 		test_must_fail git rebase $opt --reapply-cherry-picks A
>      + 	"
>      +
>      ++	test_expect_success "$opt incompatible with --rebase-merges" "
>      ++		git checkout B^0 &&
>      ++		test_must_fail git rebase $opt --rebase-merges A
>      ++	"
>      ++
>      + 	test_expect_success "$opt incompatible with --update-refs" "
>      + 		git checkout B^0 &&
>      + 		test_must_fail git rebase $opt --update-refs A
>       @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
>        		grep -e --no-autosquash err
>        	"
>        
>      -+	test_expect_success "$opt incompatible with rebase.merges" "
>      ++	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
>       +		git checkout B^0 &&
>      -+		test_must_fail git -c rebase.merges=true rebase $opt A 2>err &&
>      ++		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
>       +		grep -e --no-rebase-merges err
>       +	"
>       +
>      @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
>        		git -c rebase.autosquash=true rebase --no-autosquash $opt A
>        	"
>        
>      -+	test_expect_success "$opt okay with overridden rebase.merges" "
>      ++	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
>       +		test_when_finished \"git reset --hard B^0\" &&
>       +		git checkout B^0 &&
>      -+		git -c rebase.merges=true rebase --no-rebase-merges $opt A
>      ++		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
>       +	"
>       +
>        	test_expect_success "$opt okay with overridden rebase.updateRefs" "
>      @@ t/t3422-rebase-incompatible-options.sh: test_rebase_am_only () {
>        		git checkout B^0 &&
>       
>        ## t/t3430-rebase-merges.sh ##
>      -@@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated' '
>      - 	grep deprecated actual
>      +@@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless asked for' '
>      + 	EOF
>        '
>        
>      -+test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
>      -+	test_config rebase.merges rebase-cousins &&
>      ++test_expect_success '--rebase-merges="" is deprecated' '
>      ++	git rebase --rebase-merges="" HEAD^ 2>actual &&
>      ++	grep deprecated actual
>      ++'
>      ++
>      ++test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
>      ++	test_config rebase.rebaseMerges rebase-cousins &&
>       +	git checkout -b config-rebase-cousins main &&
>       +	git rebase HEAD^ &&
>       +	test_cmp_graph HEAD^.. <<-\EOF
>      @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
>       +	EOF
>       +'
>       +
>      -+test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '
>      -+	test_config rebase.merges no-rebase-cousins &&
>      ++test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
>      ++	test_config rebase.rebaseMerges no-rebase-cousins &&
>       +	git checkout -b override-config-no-rebase-cousins E &&
>       +	git rebase --no-rebase-merges C &&
>       +	test_cmp_graph C.. <<-\EOF
>      @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
>       +	EOF
>       +'
>       +
>      -+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '
>      -+	test_config rebase.merges rebase-cousins &&
>      ++test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.rebaseMerges=rebase-cousins' '
>      ++	test_config rebase.rebaseMerges rebase-cousins &&
>       +	git checkout -b override-config-rebase-cousins main &&
>       +	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
>       +	test_cmp_graph HEAD^.. <<-\EOF
>      @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
>       +	EOF
>       +'
>       +
>      -+test_expect_success '--rebase-merges overrides rebase.merges=false' '
>      -+	test_config rebase.merges false &&
>      ++test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
>      ++	test_config rebase.rebaseMerges false &&
>       +	git checkout -b override-config-merges-false E &&
>       +	before="$(git rev-parse --verify HEAD)" &&
>       +	test_tick &&
>      @@ t/t3430-rebase-merges.sh: test_expect_success '--rebase-merges="" is deprecated'
>       +	test_cmp_rev HEAD $before
>       +'
>       +
>      -+test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '
>      -+	test_config rebase.merges rebase-cousins &&
>      ++test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
>      ++	test_config rebase.rebaseMerges rebase-cousins &&
>       +	git checkout -b no-override-config-rebase-cousins main &&
>       +	git rebase --rebase-merges HEAD^ &&
>       +	test_cmp_graph HEAD^.. <<-\EOF
Alex Henrie· Mar 6, 2023, 17:36 UTC · re: Phillip Wood · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

On Mon, Mar 6, 2023 at 9:24 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> On 05/03/2023 05:07, Alex Henrie wrote:
Show 25 quoted lines
> > Suggestions on v5 not incorporated in v6:
>
> Thanks for adding this, it is really helpful to see what did not change
> as well as what did change. It is also helpful to briefly explain why
> you disagree with the suggestions so others can understand why you
> decided not to make these changes.
>
> > - Make --rebase-merges without an argument clobber the mode specified in
> >    rebase.rebaseMerges
>
> I'm still confused as to why we want the config value to change the
> behavior of --rebase-merges. Is it for "git pull --rebase=merges"? If
> that's the case then I think a better approach is for pull to parse
> rebase.merges and pass the appropriate argument to rebase. That way we
> don't break the expectation that command line arguments override config
> values.
>
> > - Remove the test for --rebase-merges=no-rebase-cousins overriding
> >    rebase.rebaseMerges=rebase-cousins
> > - In the tests, check the graph itself instead of checking that the
> >    graph has not changed by checking that the commit hash has not changed
>
> I'm not sure what value the existing test has if it only checks that
> HEAD is unchanged after the rebase. It could be unchanged because the
> rebase fast-forwarded or because it did nothing.

Please have a look at the email I sent before sending v6: https://lore.kernel.org/git/CAMMLpeRfD+81fsEtvKFvVepPpwYm0_-AD=QHMwhoc+LtiXpavw@mail.gmail.com/

In that email I tried to explain why I didn't incorporate your three suggestions on v5. Please let me know if it still isn't clear.

-Alex
Phillip Wood· Mar 7, 2023, 15:07 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Hi Alex
On 06/03/2023 17:36, Alex Henrie wrote:
> On Mon, Mar 6, 2023 at 9:24 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> Please have a look at the email I sent before sending v6:
> https://lore.kernel.org/git/CAMMLpeRfD+81fsEtvKFvVepPpwYm0_-AD=QHMwhoc+LtiXpavw@mail.gmail.com/
Thanks for the link, I'd missed that email.
> In that email I tried to explain why I didn't incorporate your three
> suggestions on v5. Please let me know if it still isn't clear.
I'll take a look in the next couple of days and let you know
Thanks
Phillip
> -Alex
Glen Choo· Mar 8, 2023, 00:13 UTC · re: Alex Henrie · lore

Re: [PATCH v6 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Alex Henrie <alexhenrie24@gmail.com> writes:
Show 23 quoted lines
> Changes from v5:
> - Add commit message note about --no-rebase-merges having always worked
> - Add commit message note about the test for --no-rebase-merges being
>   somewhat contrived
> - Rephrase the documentation to avoid using the phrase "By default" with
>   two different meanings, and in so doing clarify that --rebase-merges
>   without an argument is not the same as --no-rebase-merges and not
>   passing --rebase-merges is not the same as passing
>   --rebase-merges=no-rebase-cousins
> - Add commit message note about keeping --rebase-merges="" for now out
>   of an abundance of caution
> - Rephrase the warning about --rebase-merges="" to recommend
>   --rebase-merges without an argument instead, and clarify that that
>   will do the same thing
> - Remove the test for --rebase-merges=""
> - Rename the config option from rebase.merges to rebase.rebaseMerges and
>   explain why in the commit message
> - Add commit message note about why "true" is a valid option for
>   rebase.rebaseMerges and why --rebase-merges without an argument does
>   not clobber the mode specified in rebase.rebaseMerges
> - Rephrase the documentation to clarify that --rebase-merges without an
>   argument does not clobber the mode specified in rebase.rebaseMerges
> - Add another test for incompatible options

This version addresses all of the concerns I had with the previous version. Thanks!

Besides the concerns that other reviewers raised and a possible mechanical error, I don't have any outstanding concerns. I'd be happy to see this merged when those are resolved :)

Alex Henrie· Mar 12, 2023, 21:04 UTC · re: Alex Henrie · lore

[PATCH v7 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and documents the behavior of the --rebase-merges command line option to avoid confusion about how the config option and the command line option interact.

Changes from v6:
- Don't say that the default rebase-merges mode will likely change to
  rebase-cousins (although it might change to something else)
- In git-config.txt, say that rebase.rebaseMerges=true is equivalent
  to --rebase-merges=no-rebase-cousins
- Add a link from the revised paragraph in git-rebase.txt to the
  corresponding new section in git-config.txt
- Clear rebase_cousins if rebase.rebaseMerges is set to true after
  having been set to rebase-cousins or no-rebase-cousins
- Actually remove the test for --rebase-merges=""
- Remove the test for --rebase-merges=no-rebase-cousins overriding
  rebase.rebaseMerges=rebase-cousins
Suggestions on v6 not incorporated in v7:
- Make --rebase-merges without an argument clobber the mode specified in
  rebase.rebaseMerges
- In the tests, pass --force and check the graph itself or the reflog
  instead of checking that the graph has not changed by checking that
  the commit hash has not changed
- Add --rebase-merges=on as a synonym of
  --rebase-merges=no-rebase-cousins and --rebase-merges=off as a synonym
  of --no-rebase-merges
- Rewrite the documentation for rebase-cousins to more clearly explain
  the difference between rebase-cousins and no-rebase-cousins
Thanks to Phillip, Junio, Glen, and Sergey for your feedback on v6.
Alex Henrie (3):
  rebase: add documentation and test for --no-rebase-merges
  rebase: deprecate --rebase-merges=""
  rebase: add a config option for --rebase-merges
 Documentation/config/rebase.txt        | 11 ++++
 Documentation/git-rebase.txt           | 20 ++++---
 builtin/rebase.c                       | 76 +++++++++++++++++++-------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 58 ++++++++++++++++++++
 5 files changed, 156 insertions(+), 26 deletions(-)
Range-diff against v6:
1:  bf08c03ba7 = 1:  3aee0c2277 rebase: add documentation and test for --no-rebase-merges
2:  26f98b8400 = 2:  e57843d8b5 rebase: deprecate --rebase-merges=""
3:  402365256c ! 3:  b0c1a4dcb2 rebase: add a config option for --rebase-merges
    @@ Commit message
         .gitconfig.
     
         In the future, the default rebase-merges mode may change from
    -    no-rebase-cousins to rebase-cousins. Support setting rebase.rebaseMerges
    -    to the nonspecific value "true" for users who do not need or want to
    -    care about the default changing in the future. Similarly, for users who
    -    have --rebase-merges in an alias and want to get the future behavior
    -    now, use the specific rebase-merges mode from the config if a specific
    -    mode is not given on the command line.
    +    no-rebase-cousins to some other mode that doesn't exist yet. Support
    +    setting rebase.rebaseMerges to the nonspecific value "true" for users
    +    who do not need or want to care about the default changing in the
    +    future. Similarly, for users who have --rebase-merges in an alias and
    +    want to get the future behavior now, use the specific rebase-merges mode
    +    from the config if a specific mode is not given on the command line.
     
         Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
     
    @@ Documentation/config/rebase.txt: rebase.rescheduleFailedExec::
     +rebase.rebaseMerges::
     +	Whether and how to set the `--rebase-merges` option by default. Can
     +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
    -+	true is equivalent to `--rebase-merges` without an argument, setting to
    -+	`rebase-cousins` or `no-rebase-cousins` is equivalent to
    -+	`--rebase-merges` with that value as its argument, and setting to false
    -+	is equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
    ++	true or to `no-rebase-cousins` is equivalent to
    ++	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
    ++	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
    ++	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
     +	command line without an argument overrides a `rebase.rebaseMerges=false`
     +	configuration, but the absence of a specific rebase-merges mode on the
     +	command line does not counteract a specific mode set in the configuration.
    @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
     -such commits are instead rebased onto `<upstream>` (or `<onto>`, if
     -specified).
     +`no-rebase-cousins`. If the mode is not specified on the command line or in
    -+the `rebase.rebaseMerges` config option, it defaults to `no-rebase-cousins`.
    -+In `no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
    ++the `rebase.rebaseMerges` config option (see linkgit:git-config[1] or
    ++"CONFIGURATION" below), it defaults to `no-rebase-cousins`. In
    ++`no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
     +ancestor will keep their original branch point, i.e. commits that would be
     +excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
     +original ancestry by default. In `rebase-cousins` mode, such commits are
    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
     +		if (opts->config_rebase_merges < 0) {
     +			opts->config_rebase_merges = 1;
     +			parse_rebase_merges_value(opts, value);
    -+		}
    ++		} else
    ++			opts->rebase_cousins = 0;
     +		return 0;
     +	}
     +
    @@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless aske
      	EOF
      '
      
    -+test_expect_success '--rebase-merges="" is deprecated' '
    -+	git rebase --rebase-merges="" HEAD^ 2>actual &&
    -+	grep deprecated actual
    -+'
    -+
     +test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
     +	test_config rebase.rebaseMerges rebase-cousins &&
     +	git checkout -b config-rebase-cousins main &&
    @@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless aske
     +	EOF
     +'
     +
    -+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.rebaseMerges=rebase-cousins' '
    -+	test_config rebase.rebaseMerges rebase-cousins &&
    -+	git checkout -b override-config-rebase-cousins main &&
    -+	git rebase --rebase-merges=no-rebase-cousins HEAD^ &&
    -+	test_cmp_graph HEAD^.. <<-\EOF
    -+	*   Merge the topic branch '\''onebranch'\''
    -+	|\
    -+	| * D
    -+	| * G
    -+	o | H
    -+	|/
    -+	o A
    -+	EOF
    -+'
    -+
     +test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
     +	test_config rebase.rebaseMerges false &&
     +	git checkout -b override-config-merges-false E &&
-- 
2.39.2
Alex Henrie· Mar 12, 2023, 21:04 UTC · re: Alex Henrie · lore

[PATCH v7 1/3] rebase: add documentation and test for --no-rebase-merges

As far as I can tell, --no-rebase-merges has always worked, but has never been documented. It is especially important to document it before a rebase.rebaseMerges option is introduced so that users know how to override the config option on the command line. It's also important to clarify that --rebase-merges without an argument is not the same as --no-rebase-merges and not passing --rebase-merges is not the same as passing --rebase-merges=no-rebase-cousins.

A test case is necessary to make sure that --no-rebase-merges keeps working after its code is refactored in the following patches of this series. The test case is a little contrived: It's unlikely that a user would type both --rebase-merges and --no-rebase-merges at the same time. However, if an alias is defined which includes --rebase-merges, the user might decide to add --no-rebase-merges to countermand that part of the alias but leave alone other flags set by the alias.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt | 18 +++++++++++-------
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 21 insertions(+), 7 deletions(-)
Show changes to 2 files +21 −7

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..4e57a87624 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,20 +529,24 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
-By default, or when `no-rebase-cousins` was specified, commits which do not
-have `<upstream>` as direct ancestor will keep their original branch point,
-i.e. commits that would be excluded by linkgit:git-log[1]'s
-`--ancestry-path` option will keep their original ancestry by default. If
-the `rebase-cousins` mode is turned on, such commits are instead rebased
-onto `<upstream>` (or `<onto>`, if specified).
+When rebasing merges, there are two modes: `rebase-cousins` and
+`no-rebase-cousins`. If the mode is not specified, it defaults to
+`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
+`<upstream>` as direct ancestor will keep their original branch point, i.e.
+commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
+option will keep their original ancestry by default. In `rebase-cousins` mode,
+such commits are instead rebased onto `<upstream>` (or `<onto>`, if
+specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.39.2
Alex Henrie· Mar 12, 2023, 21:04 UTC · re: Alex Henrie · lore

[PATCH v7 2/3] rebase: deprecate --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges without an argument. Deprecate that syntax to avoid confusion when a rebase.rebaseMerges config option is introduced, where rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.

It is not likely that anyone is actually using this syntax, but just in case, deprecate the empty string argument instead of dropping support for it immediately.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +6 −2
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6635f10d52..c36ddc0050 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1438,7 +1438,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (rebase_merges) {
 		if (!*rebase_merges)
-			; /* default mode; do nothing */
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
 		else if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
-- 
2.39.2
Alex Henrie· Mar 12, 2023, 21:04 UTC · re: Alex Henrie · lore

[PATCH v7 3/3] 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.

In the future, the default rebase-merges mode may change from no-rebase-cousins to some other mode that doesn't exist yet. Support setting rebase.rebaseMerges to the nonspecific value "true" for users who do not need or want to care about the default changing in the future. Similarly, for users who have --rebase-merges in an alias and want to get the future behavior now, use the specific rebase-merges mode from the config if a specific mode is not given on the command line.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/config/rebase.txt        | 11 ++++
 Documentation/git-rebase.txt           | 18 +++---
 builtin/rebase.c                       | 80 ++++++++++++++++++--------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 48 ++++++++++++++++
 5 files changed, 143 insertions(+), 31 deletions(-)
Show changes to 5 files +143 −31

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3422-rebase-incompatible-options.sh, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..98e8f193d4 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,14 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.rebaseMerges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true or to `no-rebase-cousins` is equivalent to
+	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
+	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
+	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line without an argument overrides a `rebase.rebaseMerges=false`
+	configuration, but the absence of a specific rebase-merges mode on the
+	command line does not counteract a specific mode set in the configuration.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4e57a87624..7ff02f474b 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,16 +537,18 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.rebaseMerges` config option and a previous
+	`--rebase-merges`.
 +
 When rebasing merges, there are two modes: `rebase-cousins` and
-`no-rebase-cousins`. If the mode is not specified, it defaults to
-`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
-`<upstream>` as direct ancestor will keep their original branch point, i.e.
-commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
-option will keep their original ancestry by default. In `rebase-cousins` mode,
-such commits are instead rebased onto `<upstream>` (or `<onto>`, if
-specified).
+`no-rebase-cousins`. If the mode is not specified on the command line or in
+the `rebase.rebaseMerges` config option (see linkgit:git-config[1] or
+"CONFIGURATION" below), it defaults to `no-rebase-cousins`. In
+`no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
+ancestor will keep their original branch point, i.e. commits that would be
+excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
+original ancestry by default. In `rebase-cousins` mode, such commits are
+instead rebased onto `<upstream>` (or `<onto>`, if specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c36ddc0050..593db9e53f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -123,6 +123,7 @@ struct rebase_options {
 	int fork_point;
 	int update_refs;
 	int config_autosquash;
+	int config_rebase_merges;
 	int config_update_refs;
 };
 
@@ -140,6 +141,8 @@ struct rebase_options {
 		.allow_empty_message = 1,               \
 		.autosquash = -1,                       \
 		.config_autosquash = -1,                \
+		.rebase_merges = -1,                    \
+		.config_rebase_merges = -1,             \
 		.update_refs = -1,                      \
 		.config_update_refs = -1,               \
 	}
@@ -771,6 +774,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+	if (!strcmp("no-rebase-cousins", value))
+		options->rebase_cousins = 0;
+	else if (!strcmp("rebase-cousins", value))
+		options->rebase_cousins = 1;
+	else
+		die(_("Unknown rebase-merges mode: %s"), value);
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -800,6 +813,16 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.rebasemerges")) {
+		opts->config_rebase_merges = git_parse_maybe_bool(value);
+		if (opts->config_rebase_merges < 0) {
+			opts->config_rebase_merges = 1;
+			parse_rebase_merges_value(opts, value);
+		} else
+			opts->rebase_cousins = 0;
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.updaterefs")) {
 		opts->config_update_refs = git_config_bool(var, value);
 		return 0;
@@ -980,6 +1003,27 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	options->rebase_merges = !unset;
+
+	if (arg) {
+		if (!*arg) {
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
+			return 0;
+		}
+		parse_rebase_merges_value(options, arg);
+	}
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1035,7 +1079,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1137,10 +1180,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1436,21 +1478,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!*rebase_merges)
-			warning(_("--rebase-merges with an empty string "
-				  "argument is deprecated and will stop "
-				  "working in a future version of Git. Use "
-				  "--rebase-merges without an argument "
-				  "instead, which does the same thing."));
-		else if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
-		imply_merge(&options, "--rebase-merges");
-	}
-
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
 			strvec_push(&options.git_am_opts,
@@ -1513,13 +1540,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				break;
 
 		if (i >= 0 || options.type == REBASE_APPLY) {
-			if (is_merge(&options))
-				die(_("apply options and merge options "
-					  "cannot be used together"));
-			else if (options.autosquash == -1 && options.config_autosquash == 1)
+			if (options.autosquash == -1 && options.config_autosquash == 1)
 				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
+			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
 			else if (options.update_refs == -1 && options.config_update_refs == 1)
 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
+			else if (is_merge(&options))
+				die(_("apply options and merge options "
+					  "cannot be used together"));
 			else
 				options.type = REBASE_APPLY;
 		}
@@ -1530,6 +1559,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
+	if (options.rebase_merges == 1)
+		imply_merge(&options, "--rebase-merges");
+	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
 	if (options.autosquash == 1)
 		imply_merge(&options, "--autosquash");
 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index 4711b37a28..2eba00bdf5 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -85,6 +85,11 @@ test_rebase_am_only () {
 		test_must_fail git rebase $opt --reapply-cherry-picks A
 	"
 
+	test_expect_success "$opt incompatible with --rebase-merges" "
+		git checkout B^0 &&
+		test_must_fail git rebase $opt --rebase-merges A
+	"
+
 	test_expect_success "$opt incompatible with --update-refs" "
 		git checkout B^0 &&
 		test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
 		grep -e --no-autosquash err
 	"
 
+	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
+		git checkout B^0 &&
+		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
+		grep -e --no-rebase-merges err
+	"
+
 	test_expect_success "$opt incompatible with rebase.updateRefs" "
 		git checkout B^0 &&
 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
 	"
 
+	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
+		test_when_finished \"git reset --hard B^0\" &&
+		git checkout B^0 &&
+		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
+	"
+
 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
 		test_when_finished \"git reset --hard B^0\" &&
 		git checkout B^0 &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..a8d9297224 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,54 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
+	test_config rebase.rebaseMerges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
+	test_config rebase.rebaseMerges false &&
+	git checkout -b override-config-merges-false E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
+test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b no-override-config-rebase-cousins main &&
+	git rebase --rebase-merges HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.39.2
Alex Henrie· Mar 20, 2023, 05:59 UTC · re: Alex Henrie · lore

[PATCH v8 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and documents the behavior of the --rebase-merges command line option to avoid confusion about how the config option and the command line option interact.

Changes from v7:
- Make --rebase-merges without an argument clobber the mode specified in
  rebase.rebaseMerges
- Replace the test for --rebase-merges not overriding
  rebase.rebaseMerges=rebase-cousins and the test for --rebase-merges
  overriding rebase.rebaseMerges=false with a single test that
  --rebase-merges does override rebase.rebaseMerges=rebase-cousins
Thanks to Phillip, Felipe, Junio, and Glen for your feedback on v7.
Alex Henrie (3):
  rebase: add documentation and test for --no-rebase-merges
  rebase: deprecate --rebase-merges=""
  rebase: add a config option for --rebase-merges
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           | 19 ++++---
 builtin/rebase.c                       | 77 +++++++++++++++++++-------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 44 +++++++++++++++
 5 files changed, 141 insertions(+), 26 deletions(-)
Range-diff against v7:
1:  3aee0c2277 = 1:  09fb7c1b74 rebase: add documentation and test for --no-rebase-merges
2:  e57843d8b5 = 2:  a846716a4a rebase: deprecate --rebase-merges=""
3:  b0c1a4dcb2 ! 3:  b12a3610ba rebase: add a config option for --rebase-merges
    @@ Commit message
         to be clear when scrolling through values in the [rebase] section of
         .gitconfig.
     
    -    In the future, the default rebase-merges mode may change from
    -    no-rebase-cousins to some other mode that doesn't exist yet. Support
    -    setting rebase.rebaseMerges to the nonspecific value "true" for users
    -    who do not need or want to care about the default changing in the
    -    future. Similarly, for users who have --rebase-merges in an alias and
    -    want to get the future behavior now, use the specific rebase-merges mode
    -    from the config if a specific mode is not given on the command line.
    +    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>
     
    @@ Documentation/config/rebase.txt: rebase.rescheduleFailedExec::
     +	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
     +	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
     +	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
    -+	command line without an argument overrides a `rebase.rebaseMerges=false`
    -+	configuration, but the absence of a specific rebase-merges mode on the
    -+	command line does not counteract a specific mode set in the configuration.
    ++	command line, with or without an argument, overrides any
    ++	`rebase.rebaseMerges` configuration.
     
      ## Documentation/git-rebase.txt ##
     @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
    @@ Documentation/git-rebase.txt: See also INCOMPATIBLE OPTIONS below.
     +	`--rebase-merges`.
      +
      When rebasing merges, there are two modes: `rebase-cousins` and
    --`no-rebase-cousins`. If the mode is not specified, it defaults to
    --`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
    --`<upstream>` as direct ancestor will keep their original branch point, i.e.
    --commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
    --option will keep their original ancestry by default. In `rebase-cousins` mode,
    --such commits are instead rebased onto `<upstream>` (or `<onto>`, if
    --specified).
    -+`no-rebase-cousins`. If the mode is not specified on the command line or in
    -+the `rebase.rebaseMerges` config option (see linkgit:git-config[1] or
    -+"CONFIGURATION" below), it defaults to `no-rebase-cousins`. In
    -+`no-rebase-cousins` mode, commits which do not have `<upstream>` as direct
    -+ancestor will keep their original branch point, i.e. commits that would be
    -+excluded by linkgit:git-log[1]'s `--ancestry-path` option will keep their
    -+original ancestry by default. In `rebase-cousins` mode, such commits are
    -+instead rebased onto `<upstream>` (or `<onto>`, if specified).
    - +
    - It is currently only possible to recreate the merge commits using the
    - `ort` merge strategy; different merge strategies can be used only via
    + `no-rebase-cousins`. If the mode is not specified, it defaults to
     
      ## builtin/rebase.c ##
     @@ builtin/rebase.c: struct rebase_options {
    @@ builtin/rebase.c: static int parse_opt_empty(const struct option *opt, const cha
     +	struct rebase_options *options = opt->value;
     +
     +	options->rebase_merges = !unset;
    ++	options->rebase_cousins = 0;
     +
     +	if (arg) {
     +		if (!*arg) {
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
     -					  "cannot be used together"));
     -			else if (options.autosquash == -1 && options.config_autosquash == 1)
     +			if (options.autosquash == -1 && options.config_autosquash == 1)
    - 				die(_("apply options are incompatible with rebase.autosquash.  Consider adding --no-autosquash"));
    + 				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
     +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
     +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
      			else if (options.update_refs == -1 && options.config_update_refs == 1)
    @@ t/t3430-rebase-merges.sh: test_expect_success 'do not rebase cousins unless aske
     +	EOF
     +'
     +
    -+test_expect_success '--rebase-merges overrides rebase.rebaseMerges=false' '
    -+	test_config rebase.rebaseMerges false &&
    -+	git checkout -b override-config-merges-false E &&
    ++test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
    ++	test_config rebase.rebaseMerges rebase-cousins &&
    ++	git checkout -b override-config-rebase-cousins E &&
     +	before="$(git rev-parse --verify HEAD)" &&
     +	test_tick &&
     +	git rebase --rebase-merges C &&
     +	test_cmp_rev HEAD $before
     +'
    -+
    -+test_expect_success '--rebase-merges does not override rebase.rebaseMerges=rebase-cousins' '
    -+	test_config rebase.rebaseMerges rebase-cousins &&
    -+	git checkout -b no-override-config-rebase-cousins main &&
    -+	git rebase --rebase-merges HEAD^ &&
    -+	test_cmp_graph HEAD^.. <<-\EOF
    -+	*   Merge the topic branch '\''onebranch'\''
    -+	|\
    -+	| * D
    -+	| * G
    -+	|/
    -+	o H
    -+	EOF
    -+'
     +
      test_expect_success 'refs/rewritten/* is worktree-local' '
      	git worktree add wt &&
-- 
2.40.0
Alex Henrie· Mar 20, 2023, 05:59 UTC · re: Alex Henrie · lore

[PATCH v8 1/3] rebase: add documentation and test for --no-rebase-merges

As far as I can tell, --no-rebase-merges has always worked, but has never been documented. It is especially important to document it before a rebase.rebaseMerges option is introduced so that users know how to override the config option on the command line. It's also important to clarify that --rebase-merges without an argument is not the same as --no-rebase-merges and not passing --rebase-merges is not the same as passing --rebase-merges=no-rebase-cousins.

A test case is necessary to make sure that --no-rebase-merges keeps working after its code is refactored in the following patches of this series. The test case is a little contrived: It's unlikely that a user would type both --rebase-merges and --no-rebase-merges at the same time. However, if an alias is defined which includes --rebase-merges, the user might decide to add --no-rebase-merges to countermand that part of the alias but leave alone other flags set by the alias.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt | 18 +++++++++++-------
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 21 insertions(+), 7 deletions(-)
Show changes to 2 files +21 −7

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..4e57a87624 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,20 +529,24 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
-By default, or when `no-rebase-cousins` was specified, commits which do not
-have `<upstream>` as direct ancestor will keep their original branch point,
-i.e. commits that would be excluded by linkgit:git-log[1]'s
-`--ancestry-path` option will keep their original ancestry by default. If
-the `rebase-cousins` mode is turned on, such commits are instead rebased
-onto `<upstream>` (or `<onto>`, if specified).
+When rebasing merges, there are two modes: `rebase-cousins` and
+`no-rebase-cousins`. If the mode is not specified, it defaults to
+`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
+`<upstream>` as direct ancestor will keep their original branch point, i.e.
+commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
+option will keep their original ancestry by default. In `rebase-cousins` mode,
+such commits are instead rebased onto `<upstream>` (or `<onto>`, if
+specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.40.0
Alex Henrie· Mar 20, 2023, 05:59 UTC · re: Alex Henrie · lore

[PATCH v8 2/3] rebase: deprecate --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges without an argument. Deprecate that syntax to avoid confusion when a rebase.rebaseMerges config option is introduced, where rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.

It is not likely that anyone is actually using this syntax, but just in case, deprecate the empty string argument instead of dropping support for it immediately.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +6 −2
diff --git a/builtin/rebase.c b/builtin/rebase.c
index dd31d5ab91..4b3f29a449 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1141,7 +1141,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1439,7 +1439,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (rebase_merges) {
 		if (!*rebase_merges)
-			; /* default mode; do nothing */
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
 		else if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
-- 
2.40.0
Alex Henrie· Mar 20, 2023, 05:59 UTC · re: Alex Henrie · lore

[PATCH v8 3/3] 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>
---
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           |  3 +-
 builtin/rebase.c                       | 81 ++++++++++++++++++--------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 34 +++++++++++
 5 files changed, 121 insertions(+), 24 deletions(-)
Show changes to 5 files +121 −24

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3422-rebase-incompatible-options.sh, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..afaf6dad99 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.rebaseMerges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true or to `no-rebase-cousins` is equivalent to
+	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
+	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
+	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line, with or without an argument, overrides any
+	`rebase.rebaseMerges` configuration.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4e57a87624..e7b39ad244 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.rebaseMerges` config option and a previous
+	`--rebase-merges`.
 +
 When rebasing merges, there are two modes: `rebase-cousins` and
 `no-rebase-cousins`. If the mode is not specified, it defaults to
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 4b3f29a449..fd284e24ab 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -124,6 +124,7 @@ struct rebase_options {
 	int fork_point;
 	int update_refs;
 	int config_autosquash;
+	int config_rebase_merges;
 	int config_update_refs;
 };
 
@@ -141,6 +142,8 @@ struct rebase_options {
 		.allow_empty_message = 1,               \
 		.autosquash = -1,                       \
 		.config_autosquash = -1,                \
+		.rebase_merges = -1,                    \
+		.config_rebase_merges = -1,             \
 		.update_refs = -1,                      \
 		.config_update_refs = -1,               \
 	}
@@ -772,6 +775,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+	if (!strcmp("no-rebase-cousins", value))
+		options->rebase_cousins = 0;
+	else if (!strcmp("rebase-cousins", value))
+		options->rebase_cousins = 1;
+	else
+		die(_("Unknown rebase-merges mode: %s"), value);
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -801,6 +814,16 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.rebasemerges")) {
+		opts->config_rebase_merges = git_parse_maybe_bool(value);
+		if (opts->config_rebase_merges < 0) {
+			opts->config_rebase_merges = 1;
+			parse_rebase_merges_value(opts, value);
+		} else
+			opts->rebase_cousins = 0;
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.updaterefs")) {
 		opts->config_update_refs = git_config_bool(var, value);
 		return 0;
@@ -981,6 +1004,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	options->rebase_merges = !unset;
+	options->rebase_cousins = 0;
+
+	if (arg) {
+		if (!*arg) {
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
+			return 0;
+		}
+		parse_rebase_merges_value(options, arg);
+	}
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1036,7 +1081,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1138,10 +1182,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1437,21 +1480,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!*rebase_merges)
-			warning(_("--rebase-merges with an empty string "
-				  "argument is deprecated and will stop "
-				  "working in a future version of Git. Use "
-				  "--rebase-merges without an argument "
-				  "instead, which does the same thing."));
-		else if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
-		imply_merge(&options, "--rebase-merges");
-	}
-
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
 			strvec_push(&options.git_am_opts,
@@ -1514,13 +1542,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				break;
 
 		if (i >= 0 || options.type == REBASE_APPLY) {
-			if (is_merge(&options))
-				die(_("apply options and merge options "
-					  "cannot be used together"));
-			else if (options.autosquash == -1 && options.config_autosquash == 1)
+			if (options.autosquash == -1 && options.config_autosquash == 1)
 				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
+			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
 			else if (options.update_refs == -1 && options.config_update_refs == 1)
 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
+			else if (is_merge(&options))
+				die(_("apply options and merge options "
+					  "cannot be used together"));
 			else
 				options.type = REBASE_APPLY;
 		}
@@ -1531,6 +1561,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
+	if (options.rebase_merges == 1)
+		imply_merge(&options, "--rebase-merges");
+	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
 	if (options.autosquash == 1)
 		imply_merge(&options, "--autosquash");
 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index 4711b37a28..2eba00bdf5 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -85,6 +85,11 @@ test_rebase_am_only () {
 		test_must_fail git rebase $opt --reapply-cherry-picks A
 	"
 
+	test_expect_success "$opt incompatible with --rebase-merges" "
+		git checkout B^0 &&
+		test_must_fail git rebase $opt --rebase-merges A
+	"
+
 	test_expect_success "$opt incompatible with --update-refs" "
 		git checkout B^0 &&
 		test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
 		grep -e --no-autosquash err
 	"
 
+	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
+		git checkout B^0 &&
+		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
+		grep -e --no-rebase-merges err
+	"
+
 	test_expect_success "$opt incompatible with rebase.updateRefs" "
 		git checkout B^0 &&
 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
 	"
 
+	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
+		test_when_finished \"git reset --hard B^0\" &&
+		git checkout B^0 &&
+		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
+	"
+
 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
 		test_when_finished \"git reset --hard B^0\" &&
 		git checkout B^0 &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..f03599c63b 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,40 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
+	test_config rebase.rebaseMerges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b override-config-rebase-cousins E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.40.0
Phillip Wood· Mar 22, 2023, 16:54 UTC · re: Alex Henrie · lore

Re: [PATCH v8 3/3] rebase: add a config option for --rebase-merges

Hi Alex
On 20/03/2023 05:59, Alex Henrie wrote:
Show 18 quoted lines
> 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.

Hurray! Thanks for re-rolling. I've left a couple of comments below, I had hoped this would be the final version but I've realized that the way you've rearranged the error handling for apply and merge options is not a good idea (see below). Apart from that I'm happy.

Show 27 quoted lines
> Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
> ---
>   Documentation/config/rebase.txt        | 10 ++++
>   Documentation/git-rebase.txt           |  3 +-
>   builtin/rebase.c                       | 81 ++++++++++++++++++--------
>   t/t3422-rebase-incompatible-options.sh | 17 ++++++
>   t/t3430-rebase-merges.sh               | 34 +++++++++++
>   5 files changed, 121 insertions(+), 24 deletions(-)
> 
> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
> index f19bd0e040..afaf6dad99 100644
> --- a/Documentation/config/rebase.txt
> +++ b/Documentation/config/rebase.txt
> @@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
>   
>   rebase.forkPoint::
>   	If set to false set `--no-fork-point` option by default.
> +
> +rebase.rebaseMerges::
> +	Whether and how to set the `--rebase-merges` option by default. Can
> +	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
> +	true or to `no-rebase-cousins` is equivalent to
> +	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
> +	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
> +	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
> +	command line, with or without an argument, overrides any
> +	`rebase.rebaseMerges` configuration.
Sounds good
Show 8 quoted lines
> [...]   
> +	if (!strcmp(var, "rebase.rebasemerges")) {
> +		opts->config_rebase_merges = git_parse_maybe_bool(value);
> +		if (opts->config_rebase_merges < 0) {
> +			opts->config_rebase_merges = 1;
> +			parse_rebase_merges_value(opts, value);
> +		} else
> +			opts->rebase_cousins = 0;

The "else" clause should have braces because the "if" cause requires them (see Documentation/CodingGuidelines). I don't think it is worth re-rolling just for this though.

Show 7 quoted lines
> [...]   
> +static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +
> +	options->rebase_merges = !unset;
> +	options->rebase_cousins = 0;

This makes --rebase-merges without an option override rebase.rebaseMerges - good.

 > [...]
Show 7 quoted lines
> @@ -1514,13 +1542,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   				break;
>   
>   		if (i >= 0 || options.type == REBASE_APPLY) {
> -			if (is_merge(&options))
> -				die(_("apply options and merge options "
> -					  "cannot be used together"));

This isn't a new change but having thought about it I'm not sure moving this code is a good idea. If the user runs

	git -c rebase.updateRefs=true rebase --whitespace=fix --exec "make test"

then instead of getting an message saying that they cannot use apply and merge options together they'll get a message suggesting they pass --no-update-refs which wont fix the problem for them.

Show 13 quoted lines
> -			else if (options.autosquash == -1 && options.config_autosquash == 1)
> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>   				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
> +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>   			else if (options.update_refs == -1 && options.config_update_refs == 1)
>   				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
> +			else if (is_merge(&options))
> +				die(_("apply options and merge options "
> +					  "cannot be used together"));
>   			else
>   				options.type = REBASE_APPLY;
>   		}
Best Wishes
Phillip
Junio C Hamano· Mar 23, 2023, 18:45 UTC · re: Phillip Wood · lore

Re: [PATCH v8 3/3] rebase: add a config option for --rebase-merges

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 15 quoted lines
>> @@ -1514,13 +1542,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>>   				break;
>>     		if (i >= 0 || options.type == REBASE_APPLY) {
>> -			if (is_merge(&options))
>> -				die(_("apply options and merge options "
>> -					  "cannot be used together"));
>
> This isn't a new change but having thought about it I'm not sure
> moving this code is a good idea. If the user runs
>
> 	git -c rebase.updateRefs=true rebase --whitespace=fix --exec "make test"
>
> then instead of getting an message saying that they cannot use apply
> and merge options together they'll get a message suggesting they pass
> --no-update-refs which wont fix the problem for them.
Hmph.  Instead of dying here, ...
Show 6 quoted lines
>> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>>   				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
>> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
>> +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>>   			else if (options.update_refs == -1 && options.config_update_refs == 1)
>>   				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
... we get caught here, and the next one is not triggered.
Show 5 quoted lines
>> +			else if (is_merge(&options))
>> +				die(_("apply options and merge options "
>> +					  "cannot be used together"));
>>   			else
>>   				options.type = REBASE_APPLY;

What's the reason why "cannot be used together" is moved to the last in the chain?

The first two new conditions in this chain try to catch an invocation with some apply-specific command line option (e.g. "--whitespace=fix") when used with configuration variables specific to the merge-backend (e.g. "rebase.merges") and suggest overriding the configuration from the command line, and I suspect that the motivation behind this change is that their error messages are more specific than the generic "apply and merge do not mix".

But are these two new errors and hints universally a good suggestion to make? I actually think a command line option forcing us to use the apply backend should simply silently ignore (aka "override") configuration variables that take effect only when we are using the merge-backend. "git rebase --whitespace=fix --rebase-merges", giving both from the command line, is making conflicting and unsatisfyable request, and it is worth giving an error message.

But if you configure rebase.autosquash only because you want to it to be in effect for your invocations of "git rebase -i", you shouldn't have to override it from the command line when you say "git rebase" or "git rebase --whitespace=fix", should you?

Thanks.
Phillip Wood· Mar 24, 2023, 14:52 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 3/3] rebase: add a config option for --rebase-merges

Hi Junio
On 23/03/2023 18:45, Junio C Hamano wrote:
Show 58 quoted lines
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>>> @@ -1514,13 +1542,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>>>    				break;
>>>      		if (i >= 0 || options.type == REBASE_APPLY) {
>>> -			if (is_merge(&options))
>>> -				die(_("apply options and merge options "
>>> -					  "cannot be used together"));
>>
>> This isn't a new change but having thought about it I'm not sure
>> moving this code is a good idea. If the user runs
>>
>> 	git -c rebase.updateRefs=true rebase --whitespace=fix --exec "make test"
>>
>> then instead of getting an message saying that they cannot use apply
>> and merge options together they'll get a message suggesting they pass
>> --no-update-refs which wont fix the problem for them.
> 
> Hmph.  Instead of dying here, ...
> 
>>> +			if (options.autosquash == -1 && options.config_autosquash == 1)
>>>    				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
>>> +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
>>> +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>>>    			else if (options.update_refs == -1 && options.config_update_refs == 1)
>>>    				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
> 
> ... we get caught here, and the next one is not triggered.
> 
>>> +			else if (is_merge(&options))
>>> +				die(_("apply options and merge options "
>>> +					  "cannot be used together"));
>>>    			else
>>>    				options.type = REBASE_APPLY;
> 
> What's the reason why "cannot be used together" is moved to the last
> in the chain?
> 
> The first two new conditions in this chain try to catch an
> invocation with some apply-specific command line option
> (e.g. "--whitespace=fix") when used with configuration variables
> specific to the merge-backend (e.g. "rebase.merges") and suggest
> overriding the configuration from the command line, and I suspect
> that the motivation behind this change is that their error messages
> are more specific than the generic "apply and merge do not mix".
> 
> But are these two new errors and hints universally a good suggestion
> to make?  I actually think a command line option forcing us to use
> the apply backend should simply silently ignore (aka "override")
> configuration variables that take effect only when we are using the
> merge-backend.  "git rebase --whitespace=fix --rebase-merges",
> giving both from the command line, is making conflicting and
> unsatisfyable request, and it is worth giving an error message.
> 
> But if you configure rebase.autosquash only because you want to it
> to be in effect for your invocations of "git rebase -i", you
> shouldn't have to override it from the command line when you say
> "git rebase" or "git rebase --whitespace=fix", should you?

I think there are two conflicting viewpoints here which depend on what one thinks the user wants when they set these configuration variables. As I understand it Elijah's thinking was that if the user set rebase.autosquash they'd be surprised when their fixup commits were not squashed by

	git rebase --whitespace=fix
and that's why his patch series changed things to error out.

The other point of view is that the user expects that these configuration variables to apply only when they are using the "merge" backend and so we should not error out.

Personally I lean a little more towards the latter but I don't feel that strongly about it and can see an argument for having either behavior. I do think we should leave the ordering alone though so the user gets a useful error message in the case of "git rebase --exec 'make test' --whitespace=fix"

Best Wishes
Phillip
> Thanks.
Alex Henrie· Mar 25, 2023, 05:23 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 3/3] rebase: add a config option for --rebase-merges

On Thu, Mar 23, 2023 at 12:45 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 46 quoted lines
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> >> @@ -1514,13 +1542,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >>                              break;
> >>              if (i >= 0 || options.type == REBASE_APPLY) {
> >> -                    if (is_merge(&options))
> >> -                            die(_("apply options and merge options "
> >> -                                      "cannot be used together"));
> >
> > This isn't a new change but having thought about it I'm not sure
> > moving this code is a good idea. If the user runs
> >
> >       git -c rebase.updateRefs=true rebase --whitespace=fix --exec "make test"
> >
> > then instead of getting an message saying that they cannot use apply
> > and merge options together they'll get a message suggesting they pass
> > --no-update-refs which wont fix the problem for them.
>
> Hmph.  Instead of dying here, ...
>
> >> +                    if (options.autosquash == -1 && options.config_autosquash == 1)
> >>                              die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
> >> +                    else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
> >> +                            die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
> >>                      else if (options.update_refs == -1 && options.config_update_refs == 1)
> >>                              die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
>
> ... we get caught here, and the next one is not triggered.
>
> >> +                    else if (is_merge(&options))
> >> +                            die(_("apply options and merge options "
> >> +                                      "cannot be used together"));
> >>                      else
> >>                              options.type = REBASE_APPLY;
>
> What's the reason why "cannot be used together" is moved to the last
> in the chain?
>
> The first two new conditions in this chain try to catch an
> invocation with some apply-specific command line option
> (e.g. "--whitespace=fix") when used with configuration variables
> specific to the merge-backend (e.g. "rebase.merges") and suggest
> overriding the configuration from the command line, and I suspect
> that the motivation behind this change is that their error messages
> are more specific than the generic "apply and merge do not mix".

Phillip specifically asked for `git -c rebase.rebaseMerges=true rebase --whitespace=fix` to print "error: apply options are incompatible with rebase.rebaseMerges. Consider adding --no-rebase-merges".[1] I could have sworn that "if (is_merge(&options))" had to be after "if (options.rebase_merges == -1 && options.config_rebase_merges == 1)" to make that happen, but now it works without that change. I must have been debugging with some intermediate version that still had 'imply_merge(&options, "--rebase-merges")' before this if chain. I'll send a v9 that puts "if (is_merge(&options))" back at the top.

Thanks for your attention to detail!
-Alex
[1] https://lore.kernel.org/git/7ba8a92d-8c94-5226-5416-6ed3a8e2e389@dunelm.org.uk/
Alex Henrie· Mar 25, 2023, 05:21 UTC · re: Phillip Wood · lore

Re: [PATCH v8 3/3] rebase: add a config option for --rebase-merges

On Wed, Mar 22, 2023 at 10:54 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> Hurray! Thanks for re-rolling.
Thanks for making sure that we got the UI right!
> On 20/03/2023 05:59, Alex Henrie wrote:
Show 11 quoted lines
> > +     if (!strcmp(var, "rebase.rebasemerges")) {
> > +             opts->config_rebase_merges = git_parse_maybe_bool(value);
> > +             if (opts->config_rebase_merges < 0) {
> > +                     opts->config_rebase_merges = 1;
> > +                     parse_rebase_merges_value(opts, value);
> > +             } else
> > +                     opts->rebase_cousins = 0;
>
> The "else" clause should have braces because the "if" cause requires
> them (see Documentation/CodingGuidelines). I don't think it is worth
> re-rolling just for this though.

Thanks for pointing out that documentation. I'm going to make a v9 anyway, and I'll add the braces then. By the way, actions speak louder than words: While writing the patch, I found several examples of the braces being omitted in cases like this in other places in rebase.c, so I assumed that that was the preferred style here. If you want to encourage people to follow the CodingGuidelines document, the best way would be to clean up the existing code to conform to it.

-Alex
Alex Henrie· Mar 26, 2023, 03:06 UTC · re: Alex Henrie · lore

[PATCH v9 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and documents the behavior of the --rebase-merges command line option to avoid confusion about how the config option and the command line option interact.

Changes from v8:
- Add braces around one-line else clause
- Remove unnecessary change to error message priority
Thanks to Phillip, Junio, Johannes and Sergey for your feedback on v8.
Alex Henrie (3):
  rebase: add documentation and test for --no-rebase-merges
  rebase: deprecate --rebase-merges=""
  rebase: add a config option for --rebase-merges
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           | 19 ++++---
 builtin/rebase.c                       | 70 ++++++++++++++++++++------
 t/t3422-rebase-incompatible-options.sh | 17 +++++++
 t/t3430-rebase-merges.sh               | 44 ++++++++++++++++
 5 files changed, 138 insertions(+), 22 deletions(-)
Range-diff against v8:
1:  09fb7c1b74 = 1:  a22b9d0da2 rebase: add documentation and test for --no-rebase-merges
2:  a846716a4a = 2:  112fee4833 rebase: deprecate --rebase-merges=""
3:  b12a3610ba ! 3:  868899cd6d rebase: add a config option for --rebase-merges
    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
     +		if (opts->config_rebase_merges < 0) {
     +			opts->config_rebase_merges = 1;
     +			parse_rebase_merges_value(opts, value);
    -+		} else
    ++		} else {
     +			opts->rebase_cousins = 0;
    ++		}
     +		return 0;
     +	}
     +
    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
      		if (ignore_whitespace)
      			strvec_push(&options.git_am_opts,
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 				break;
    - 
    - 		if (i >= 0 || options.type == REBASE_APPLY) {
    --			if (is_merge(&options))
    --				die(_("apply options and merge options "
    --					  "cannot be used together"));
    --			else if (options.autosquash == -1 && options.config_autosquash == 1)
    -+			if (options.autosquash == -1 && options.config_autosquash == 1)
    + 					  "cannot be used together"));
    + 			else if (options.autosquash == -1 && options.config_autosquash == 1)
      				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
     +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
     +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
      			else if (options.update_refs == -1 && options.config_update_refs == 1)
      				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
    -+			else if (is_merge(&options))
    -+				die(_("apply options and merge options "
    -+					  "cannot be used together"));
      			else
    - 				options.type = REBASE_APPLY;
    - 		}
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
      	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
      			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
-- 
2.40.0
Alex Henrie· Mar 26, 2023, 03:06 UTC · re: Alex Henrie · lore

[PATCH v9 1/3] rebase: add documentation and test for --no-rebase-merges

As far as I can tell, --no-rebase-merges has always worked, but has never been documented. It is especially important to document it before a rebase.rebaseMerges option is introduced so that users know how to override the config option on the command line. It's also important to clarify that --rebase-merges without an argument is not the same as --no-rebase-merges and not passing --rebase-merges is not the same as passing --rebase-merges=no-rebase-cousins.

A test case is necessary to make sure that --no-rebase-merges keeps working after its code is refactored in the following patches of this series. The test case is a little contrived: It's unlikely that a user would type both --rebase-merges and --no-rebase-merges at the same time. However, if an alias is defined which includes --rebase-merges, the user might decide to add --no-rebase-merges to countermand that part of the alias but leave alone other flags set by the alias.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 Documentation/git-rebase.txt | 18 +++++++++++-------
 t/t3430-rebase-merges.sh     | 10 ++++++++++
 2 files changed, 21 insertions(+), 7 deletions(-)
Show changes to 2 files +21 −7

Documentation/git-rebase.txt, t/t3430-rebase-merges.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9a295bcee4..4e57a87624 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -529,20 +529,24 @@ See also INCOMPATIBLE OPTIONS below.
 
 -r::
 --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::
+--no-rebase-merges::
 	By default, a rebase will simply drop merge commits from the todo
 	list, and put the rebased commits into a single, linear branch.
 	With `--rebase-merges`, the rebase will instead try to preserve
 	the branching structure within the commits that are to be rebased,
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
-	resolved/re-applied manually.
+	resolved/re-applied manually. `--no-rebase-merges` can be used to
+	countermand a previous `--rebase-merges`.
 +
-By default, or when `no-rebase-cousins` was specified, commits which do not
-have `<upstream>` as direct ancestor will keep their original branch point,
-i.e. commits that would be excluded by linkgit:git-log[1]'s
-`--ancestry-path` option will keep their original ancestry by default. If
-the `rebase-cousins` mode is turned on, such commits are instead rebased
-onto `<upstream>` (or `<onto>`, if specified).
+When rebasing merges, there are two modes: `rebase-cousins` and
+`no-rebase-cousins`. If the mode is not specified, it defaults to
+`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have
+`<upstream>` as direct ancestor will keep their original branch point, i.e.
+commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path`
+option will keep their original ancestry by default. In `rebase-cousins` mode,
+such commits are instead rebased onto `<upstream>` (or `<onto>`, if
+specified).
 +
 It is currently only possible to recreate the merge commits using the
 `ort` merge strategy; different merge strategies can be used only via
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f..d46d9545f2 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' '
 	EOF
 '
 
+test_expect_success '--no-rebase-merges countermands --rebase-merges' '
+	git checkout -b no-rebase-merges E &&
+	git rebase --rebase-merges --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
 test_expect_success 'do not rebase cousins unless asked for' '
 	git checkout -b cousins main &&
 	before="$(git rev-parse --verify HEAD)" &&
-- 
2.40.0
Alex Henrie· Mar 26, 2023, 03:06 UTC · re: Alex Henrie · lore

[PATCH v9 2/3] rebase: deprecate --rebase-merges=""

The unusual syntax --rebase-merges="" (that is, --rebase-merges with an empty string argument) has been an undocumented synonym of --rebase-merges without an argument. Deprecate that syntax to avoid confusion when a rebase.rebaseMerges config option is introduced, where rebase.rebaseMerges="" will be equivalent to --no-rebase-merges.

It is not likely that anyone is actually using this syntax, but just in case, deprecate the empty string argument instead of dropping support for it immediately.

Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +6 −2
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 5b7b908b66..9448f7d87f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1141,7 +1141,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)""},
+			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1439,7 +1439,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (rebase_merges) {
 		if (!*rebase_merges)
-			; /* default mode; do nothing */
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
 		else if (!strcmp("rebase-cousins", rebase_merges))
 			options.rebase_cousins = 1;
 		else if (strcmp("no-rebase-cousins", rebase_merges))
-- 
2.40.0
Alex Henrie· Mar 26, 2023, 03:06 UTC · re: Alex Henrie · lore

[PATCH v9 3/3] 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>
---
 Documentation/config/rebase.txt        | 10 ++++
 Documentation/git-rebase.txt           |  3 +-
 builtin/rebase.c                       | 74 +++++++++++++++++++-------
 t/t3422-rebase-incompatible-options.sh | 17 ++++++
 t/t3430-rebase-merges.sh               | 34 ++++++++++++
 5 files changed, 118 insertions(+), 20 deletions(-)
Show changes to 5 files +118 −20

Documentation/config/rebase.txt, Documentation/git-rebase.txt, builtin/rebase.c, t/t3422-rebase-incompatible-options.sh, t/t3430-rebase-merges.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index f19bd0e040..afaf6dad99 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::
 
 rebase.forkPoint::
 	If set to false set `--no-fork-point` option by default.
+
+rebase.rebaseMerges::
+	Whether and how to set the `--rebase-merges` option by default. Can
+	be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to
+	true or to `no-rebase-cousins` is equivalent to
+	`--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is
+	equivalent to `--rebase-merges=rebase-cousins`, and setting to false is
+	equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the
+	command line, with or without an argument, overrides any
+	`rebase.rebaseMerges` configuration.
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4e57a87624..e7b39ad244 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.
 	by recreating the merge commits. Any resolved merge conflicts or
 	manual amendments in these merge commits will have to be
 	resolved/re-applied manually. `--no-rebase-merges` can be used to
-	countermand a previous `--rebase-merges`.
+	countermand both the `rebase.rebaseMerges` config option and a previous
+	`--rebase-merges`.
 +
 When rebasing merges, there are two modes: `rebase-cousins` and
 `no-rebase-cousins`. If the mode is not specified, it defaults to
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 9448f7d87f..beebeb8f52 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -124,6 +124,7 @@ struct rebase_options {
 	int fork_point;
 	int update_refs;
 	int config_autosquash;
+	int config_rebase_merges;
 	int config_update_refs;
 };
 
@@ -141,6 +142,8 @@ struct rebase_options {
 		.allow_empty_message = 1,               \
 		.autosquash = -1,                       \
 		.config_autosquash = -1,                \
+		.rebase_merges = -1,                    \
+		.config_rebase_merges = -1,             \
 		.update_refs = -1,                      \
 		.config_update_refs = -1,               \
 	}
@@ -772,6 +775,16 @@ static int run_specific_rebase(struct rebase_options *opts)
 	return status ? -1 : 0;
 }
 
+static void parse_rebase_merges_value(struct rebase_options *options, const char *value)
+{
+	if (!strcmp("no-rebase-cousins", value))
+		options->rebase_cousins = 0;
+	else if (!strcmp("rebase-cousins", value))
+		options->rebase_cousins = 1;
+	else
+		die(_("Unknown rebase-merges mode: %s"), value);
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -801,6 +814,17 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.rebasemerges")) {
+		opts->config_rebase_merges = git_parse_maybe_bool(value);
+		if (opts->config_rebase_merges < 0) {
+			opts->config_rebase_merges = 1;
+			parse_rebase_merges_value(opts, value);
+		} else {
+			opts->rebase_cousins = 0;
+		}
+		return 0;
+	}
+
 	if (!strcmp(var, "rebase.updaterefs")) {
 		opts->config_update_refs = git_config_bool(var, value);
 		return 0;
@@ -981,6 +1005,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
 	return 0;
 }
 
+static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+
+	options->rebase_merges = !unset;
+	options->rebase_cousins = 0;
+
+	if (arg) {
+		if (!*arg) {
+			warning(_("--rebase-merges with an empty string "
+				  "argument is deprecated and will stop "
+				  "working in a future version of Git. Use "
+				  "--rebase-merges without an argument "
+				  "instead, which does the same thing."));
+			return 0;
+		}
+		parse_rebase_merges_value(options, arg);
+	}
+
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1036,7 +1082,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id branch_base;
 	int ignore_whitespace = 0;
 	const char *gpg_sign = NULL;
-	const char *rebase_merges = NULL;
 	struct string_list strategy_options = STRING_LIST_INIT_NODUP;
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
@@ -1138,10 +1183,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			   &options.allow_empty_message,
 			   N_("allow rebasing commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
-		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
-			N_("mode"),
+		OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-			PARSE_OPT_OPTARG, NULL, (intptr_t)"no-rebase-cousins"},
+			PARSE_OPT_OPTARG, parse_opt_rebase_merges),
 		OPT_BOOL(0, "fork-point", &options.fork_point,
 			 N_("use 'merge-base --fork-point' to refine upstream")),
 		OPT_STRING('s', "strategy", &options.strategy,
@@ -1437,21 +1481,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.exec.nr)
 		imply_merge(&options, "--exec");
 
-	if (rebase_merges) {
-		if (!*rebase_merges)
-			warning(_("--rebase-merges with an empty string "
-				  "argument is deprecated and will stop "
-				  "working in a future version of Git. Use "
-				  "--rebase-merges without an argument "
-				  "instead, which does the same thing."));
-		else if (!strcmp("rebase-cousins", rebase_merges))
-			options.rebase_cousins = 1;
-		else if (strcmp("no-rebase-cousins", rebase_merges))
-			die(_("Unknown mode: %s"), rebase_merges);
-		options.rebase_merges = 1;
-		imply_merge(&options, "--rebase-merges");
-	}
-
 	if (options.type == REBASE_APPLY) {
 		if (ignore_whitespace)
 			strvec_push(&options.git_am_opts,
@@ -1519,6 +1548,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 					  "cannot be used together"));
 			else if (options.autosquash == -1 && options.config_autosquash == 1)
 				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
+			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
+				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
 			else if (options.update_refs == -1 && options.config_update_refs == 1)
 				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
 			else
@@ -1531,6 +1562,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
 			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
 
+	if (options.rebase_merges == 1)
+		imply_merge(&options, "--rebase-merges");
+	options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges :
+				((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0);
+
 	if (options.autosquash == 1)
 		imply_merge(&options, "--autosquash");
 	options.autosquash = (options.autosquash >= 0) ? options.autosquash :
diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh
index 4711b37a28..2eba00bdf5 100755
--- a/t/t3422-rebase-incompatible-options.sh
+++ b/t/t3422-rebase-incompatible-options.sh
@@ -85,6 +85,11 @@ test_rebase_am_only () {
 		test_must_fail git rebase $opt --reapply-cherry-picks A
 	"
 
+	test_expect_success "$opt incompatible with --rebase-merges" "
+		git checkout B^0 &&
+		test_must_fail git rebase $opt --rebase-merges A
+	"
+
 	test_expect_success "$opt incompatible with --update-refs" "
 		git checkout B^0 &&
 		test_must_fail git rebase $opt --update-refs A
@@ -101,6 +106,12 @@ test_rebase_am_only () {
 		grep -e --no-autosquash err
 	"
 
+	test_expect_success "$opt incompatible with rebase.rebaseMerges" "
+		git checkout B^0 &&
+		test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err &&
+		grep -e --no-rebase-merges err
+	"
+
 	test_expect_success "$opt incompatible with rebase.updateRefs" "
 		git checkout B^0 &&
 		test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
@@ -113,6 +124,12 @@ test_rebase_am_only () {
 		git -c rebase.autosquash=true rebase --no-autosquash $opt A
 	"
 
+	test_expect_success "$opt okay with overridden rebase.rebaseMerges" "
+		test_when_finished \"git reset --hard B^0\" &&
+		git checkout B^0 &&
+		git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A
+	"
+
 	test_expect_success "$opt okay with overridden rebase.updateRefs" "
 		test_when_finished \"git reset --hard B^0\" &&
 		git checkout B^0 &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index d46d9545f2..f03599c63b 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -278,6 +278,40 @@ test_expect_success 'do not rebase cousins unless asked for' '
 	EOF
 '
 
+test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b config-rebase-cousins main &&
+	git rebase HEAD^ &&
+	test_cmp_graph HEAD^.. <<-\EOF
+	*   Merge the topic branch '\''onebranch'\''
+	|\
+	| * D
+	| * G
+	|/
+	o H
+	EOF
+'
+
+test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
+	test_config rebase.rebaseMerges no-rebase-cousins &&
+	git checkout -b override-config-no-rebase-cousins E &&
+	git rebase --no-rebase-merges C &&
+	test_cmp_graph C.. <<-\EOF
+	* B
+	* D
+	o C
+	EOF
+'
+
+test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
+	test_config rebase.rebaseMerges rebase-cousins &&
+	git checkout -b override-config-rebase-cousins E &&
+	before="$(git rev-parse --verify HEAD)" &&
+	test_tick &&
+	git rebase --rebase-merges C &&
+	test_cmp_rev HEAD $before
+'
+
 test_expect_success 'refs/rewritten/* is worktree-local' '
 	git worktree add wt &&
 	cat >wt/script-from-scratch <<-\EOF &&
-- 
2.40.0
Phillip Wood· Mar 26, 2023, 15:12 UTC · re: Alex Henrie · lore

Re: [PATCH v9 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Hi Alex
On 26/03/2023 04:06, Alex Henrie wrote:
Show 11 quoted lines
> This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and
> documents the behavior of the --rebase-merges command line option to
> avoid confusion about how the config option and the command line option
> interact.
> 
> Changes from v8:
> - Add braces around one-line else clause
> - Remove unnecessary change to error message priority

The range-diff looks good to me. This iteration addresses all of my outstanding concerns.

Thanks
Phillip
Show 57 quoted lines
> Thanks to Phillip, Junio, Johannes and Sergey for your feedback on v8.
> 
> Alex Henrie (3):
>    rebase: add documentation and test for --no-rebase-merges
>    rebase: deprecate --rebase-merges=""
>    rebase: add a config option for --rebase-merges
> 
>   Documentation/config/rebase.txt        | 10 ++++
>   Documentation/git-rebase.txt           | 19 ++++---
>   builtin/rebase.c                       | 70 ++++++++++++++++++++------
>   t/t3422-rebase-incompatible-options.sh | 17 +++++++
>   t/t3430-rebase-merges.sh               | 44 ++++++++++++++++
>   5 files changed, 138 insertions(+), 22 deletions(-)
> 
> Range-diff against v8:
> 1:  09fb7c1b74 = 1:  a22b9d0da2 rebase: add documentation and test for --no-rebase-merges
> 2:  a846716a4a = 2:  112fee4833 rebase: deprecate --rebase-merges=""
> 3:  b12a3610ba ! 3:  868899cd6d rebase: add a config option for --rebase-merges
>      @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v
>       +		if (opts->config_rebase_merges < 0) {
>       +			opts->config_rebase_merges = 1;
>       +			parse_rebase_merges_value(opts, value);
>      -+		} else
>      ++		} else {
>       +			opts->rebase_cousins = 0;
>      ++		}
>       +		return 0;
>       +	}
>       +
>      @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix
>        		if (ignore_whitespace)
>        			strvec_push(&options.git_am_opts,
>       @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
>      - 				break;
>      -
>      - 		if (i >= 0 || options.type == REBASE_APPLY) {
>      --			if (is_merge(&options))
>      --				die(_("apply options and merge options "
>      --					  "cannot be used together"));
>      --			else if (options.autosquash == -1 && options.config_autosquash == 1)
>      -+			if (options.autosquash == -1 && options.config_autosquash == 1)
>      + 					  "cannot be used together"));
>      + 			else if (options.autosquash == -1 && options.config_autosquash == 1)
>        				die(_("apply options are incompatible with rebase.autoSquash.  Consider adding --no-autosquash"));
>       +			else if (options.rebase_merges == -1 && options.config_rebase_merges == 1)
>       +				die(_("apply options are incompatible with rebase.rebaseMerges.  Consider adding --no-rebase-merges"));
>        			else if (options.update_refs == -1 && options.config_update_refs == 1)
>        				die(_("apply options are incompatible with rebase.updateRefs.  Consider adding --no-update-refs"));
>      -+			else if (is_merge(&options))
>      -+				die(_("apply options and merge options "
>      -+					  "cannot be used together"));
>        			else
>      - 				options.type = REBASE_APPLY;
>      - 		}
>       @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
>        	options.update_refs = (options.update_refs >= 0) ? options.update_refs :
>        			     ((options.config_update_refs >= 0) ? options.config_update_refs : 0);
Junio C Hamano· Mar 27, 2023, 16:33 UTC · re: Phillip Wood · lore

Re: [PATCH v9 0/3] rebase: document, clean up, and introduce a config option for --rebase-merges

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 16 quoted lines
> Hi Alex
>
> On 26/03/2023 04:06, Alex Henrie wrote:
>> This patch series introduces a rebase.rebaseMerges config option 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. It also cleans up and
>> documents the behavior of the --rebase-merges command line option to
>> avoid confusion about how the config option and the command line option
>> interact.
>> Changes from v8:
>> - Add braces around one-line else clause
>> - Remove unnecessary change to error message priority
>
> The range-diff looks good to me. This iteration addresses all of my
> outstanding concerns.
Thanks, both.  Let's mark the topic as ready for 'next', then.

← back to recent threads