mbox series

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

Message ID 20230305050709.68736-1-alexhenrie24@gmail.com (mailing list archive)
Headers show
Series rebase: document, clean up, and introduce a config option for --rebase-merges | expand

Message

Alex Henrie March 5, 2023, 5:07 a.m. UTC
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

Comments

Sergey Organov March 5, 2023, 12:22 p.m. UTC | #1
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 March 5, 2023, 9:33 p.m. UTC | #2
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. 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 March 5, 2023, 10:54 p.m. UTC | #3
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.

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 March 6, 2023, 12:02 a.m. UTC | #4
On Sun, Mar 5, 2023 at 3:54 PM Sergey Organov <sorganov@gmail.com> wrote:
>
> 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 :-/

> > 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 March 6, 2023, 1:23 p.m. UTC | #5
Alex Henrie <alexhenrie24@gmail.com> writes:

[...]

>> > 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
Phillip Wood March 6, 2023, 4:24 p.m. UTC | #6
Hi Alex

On 05/03/2023 05:07, 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 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

> 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
Junio C Hamano March 6, 2023, 5:19 p.m. UTC | #7
Sergey Organov <sorganov@gmail.com> writes:

>> 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.
Alex Henrie March 6, 2023, 5:36 p.m. UTC | #8
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:

> > 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
Junio C Hamano March 6, 2023, 7:08 p.m. UTC | #9
Alex Henrie <alexhenrie24@gmail.com> writes:

> 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.
Phillip Wood March 7, 2023, 3:07 p.m. UTC | #10
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 March 8, 2023, 12:13 a.m. UTC | #11
Alex Henrie <alexhenrie24@gmail.com> writes:

> 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 :)