{"thread":{"id":"59286","subject":"[PATCH v3 1/3] rebase: add documentation and tests for --no-rebase-merges","startedAt":"2023-02-22T05:17:52Z","lastAt":"2023-02-23T05:35:57Z","messageCount":6,"participants":["Alex Henrie","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":3},"messages":[{"id":"472430","messageId":"20230222051709.464275-1-alexhenrie24@gmail.com","threadId":"59286","inReplyTo":null,"subject":"[PATCH v3 1/3] rebase: add documentation and tests for --no-rebase-merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2023-02-22T05:17:07Z","receivedAt":"2023-02-22T05:17:52Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"Signed-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n Documentation/git-rebase.txt |  4 +++-\n t/t3430-rebase-merges.sh     | 25 +++++++++++++++++++++++++\n 2 files changed, 28 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 9a295bcee4..c98784a0d2 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -529,13 +529,15 @@ See also INCOMPATIBLE OPTIONS below.\n \n -r::\n --rebase-merges[=(rebase-cousins|no-rebase-cousins)]::\n+--no-rebase-merges::\n \tBy default, a rebase will simply drop merge commits from the todo\n \tlist, and put the rebased commits into a single, linear branch.\n \tWith `--rebase-merges`, the rebase will instead try to preserve\n \tthe branching structure within the commits that are to be rebased,\n \tby recreating the merge commits. Any resolved merge conflicts or\n \tmanual amendments in these merge commits will have to be\n-\tresolved/re-applied manually.\n+\tresolved/re-applied manually. `--no-rebase-merges` can be used to\n+\tcountermand a previous `--rebase-merges`.\n +\n By default, or when `no-rebase-cousins` was specified, commits which do not\n have `<upstream>` as direct ancestor will keep their original branch point,\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex fa2a06c19f..e0d910c229 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -250,6 +250,31 @@ test_expect_success 'with a branch tip that was cherry-picked already' '\n \tEOF\n '\n \n+test_expect_success 'do not rebase merges unless asked to' '\n+\tgit checkout -b rebase-merges-default E &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges C &&\n+\ttest_cmp_rev HEAD $before &&\n+\ttest_tick &&\n+\tgit rebase C &&\n+\ttest_cmp_graph C.. <<-\\EOF\n+\t* B\n+\t* D\n+\to C\n+\tEOF\n+'\n+\n+test_expect_success '--no-rebase-merges countermands --rebase-merges' '\n+\tgit checkout -b no-rebase-merges E &&\n+\tgit rebase --rebase-merges --no-rebase-merges C &&\n+\ttest_cmp_graph C.. <<-\\EOF\n+\t* B\n+\t* D\n+\to C\n+\tEOF\n+'\n+\n test_expect_success 'do not rebase cousins unless asked for' '\n \tgit checkout -b cousins main &&\n \tbefore=\"$(git rev-parse --verify HEAD)\" &&\n-- \n2.39.2\n\n"},{"id":"472431","messageId":"20230222051709.464275-2-alexhenrie24@gmail.com","threadId":"59286","inReplyTo":"20230222051709.464275-1-alexhenrie24@gmail.com","subject":"[PATCH v3 2/3] rebase: stop accepting --rebase-merges=\"\"","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2023-02-22T05:17:08Z","receivedAt":"2023-02-22T05:17:53Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"The unusual syntax --rebase-merges=\"\" (that is, --rebase-merges with an\nempty string argument) has been an undocumented synonym of\n--rebase-merges=no-rebase-cousins. Stop accepting that syntax to avoid\nconfusion when a rebase.merges config option is introduced, where\nrebase.merges=\"\" will be equivalent to not passing --rebase-merges.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n builtin/rebase.c         | 6 ++----\n t/t3430-rebase-merges.sh | 6 ++++++\n 2 files changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 6635f10d52..b68fc2fbb7 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1140,7 +1140,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t{OPTION_STRING, 'r', \"rebase-merges\", &rebase_merges,\n \t\t\tN_(\"mode\"),\n \t\t\tN_(\"try to rebase merges instead of skipping them\"),\n-\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)\"\"},\n+\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)\"no-rebase-cousins\"},\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n \t\tOPT_STRING('s', \"strategy\", &options.strategy,\n@@ -1437,9 +1437,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\timply_merge(&options, \"--exec\");\n \n \tif (rebase_merges) {\n-\t\tif (!*rebase_merges)\n-\t\t\t; /* default mode; do nothing */\n-\t\telse if (!strcmp(\"rebase-cousins\", rebase_merges))\n+\t\tif (!strcmp(\"rebase-cousins\", rebase_merges))\n \t\t\toptions.rebase_cousins = 1;\n \t\telse if (strcmp(\"no-rebase-cousins\", rebase_merges))\n \t\t\tdie(_(\"Unknown mode: %s\"), rebase_merges);\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex e0d910c229..b8ba323dbc 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -293,6 +293,12 @@ test_expect_success 'do not rebase cousins unless asked for' '\n \tEOF\n '\n \n+test_expect_success '--rebase-merges=\"\" is invalid syntax' '\n+\techo \"fatal: Unknown mode: \" >expect &&\n+\t! git rebase --rebase-merges=\"\" HEAD^ 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.39.2\n\n"},{"id":"472432","messageId":"20230222051709.464275-3-alexhenrie24@gmail.com","threadId":"59286","inReplyTo":"20230222051709.464275-1-alexhenrie24@gmail.com","subject":"[PATCH v3 3/3] rebase: add a config option for --rebase-merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2023-02-22T05:17:09Z","receivedAt":"2023-02-22T05:18:27Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"The purpose of the new option is to accommodate users who would like\n--rebase-merges to be on by default and to facilitate possibly turning\non --rebase-merges by default without configuration in a future version\nof Git.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n Documentation/config/rebase.txt | 10 ++++\n Documentation/git-rebase.txt    |  3 +-\n builtin/rebase.c                | 47 ++++++++++++----\n t/t3430-rebase-merges.sh        | 96 +++++++++++++++++++++++++++++++++\n 4 files changed, 144 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex f19bd0e040..308baa9dbb 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -67,3 +67,13 @@ rebase.rescheduleFailedExec::\n \n rebase.forkPoint::\n \tIf set to false set `--no-fork-point` option by default.\n+\n+rebase.merges::\n+\tWhether and how to set the `--rebase-merges` option by default. Can\n+\tbe `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to\n+\ttrue is equivalent to `--rebase-merges` without an argument, setting to\n+\t`rebase-cousins` or `no-rebase-cousins` is equivalent to\n+\t`--rebase-merges` with that value as its argument, and setting to false\n+\tis equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the\n+\tcommand line without an argument overrides a `rebase.merges=false`\n+\tconfiguration but does not override other values of `rebase.merge`.\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex c98784a0d2..b02f9cbb8c 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -537,7 +537,8 @@ See also INCOMPATIBLE OPTIONS below.\n \tby recreating the merge commits. Any resolved merge conflicts or\n \tmanual amendments in these merge commits will have to be\n \tresolved/re-applied manually. `--no-rebase-merges` can be used to\n-\tcountermand a previous `--rebase-merges`.\n+\tcountermand both the `rebase.merges` config option and a previous\n+\t`--rebase-merges`.\n +\n By default, or when `no-rebase-cousins` was specified, commits which do not\n have `<upstream>` as direct ancestor will keep their original branch point,\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b68fc2fbb7..45cf445d42 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -771,6 +771,20 @@ static int run_specific_rebase(struct rebase_options *opts)\n \treturn status ? -1 : 0;\n }\n \n+static void parse_merges_value(struct rebase_options *options, const char *value)\n+{\n+\tif (value) {\n+\t\tif (!strcmp(\"no-rebase-cousins\", value))\n+\t\t\toptions->rebase_cousins = 0;\n+\t\telse if (!strcmp(\"rebase-cousins\", value))\n+\t\t\toptions->rebase_cousins = 1;\n+\t\telse\n+\t\t\tdie(_(\"Unknown mode: %s\"), value);\n+\t}\n+\n+\toptions->rebase_merges = 1;\n+}\n+\n static int rebase_config(const char *var, const char *value, void *data)\n {\n \tstruct rebase_options *opts = data;\n@@ -815,6 +829,13 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"rebase.merges\") && value && *value) {\n+\t\topts->rebase_merges = git_parse_maybe_bool(value);\n+\t\tif (opts->rebase_merges < 0)\n+\t\t\tparse_merges_value(opts, value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"rebase.backend\")) {\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n@@ -980,6 +1001,18 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n \treturn 0;\n }\n \n+static int parse_opt_merges(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\n+\tif (unset)\n+\t\toptions->rebase_merges = 0;\n+\telse\n+\t\tparse_merges_value(options, arg);\n+\n+\treturn 0;\n+}\n+\n static void NORETURN error_on_missing_default_upstream(void)\n {\n \tstruct branch *current_branch = branch_get(NULL);\n@@ -1035,7 +1068,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id branch_base;\n \tint ignore_whitespace = 0;\n \tconst char *gpg_sign = NULL;\n-\tconst char *rebase_merges = NULL;\n \tstruct string_list strategy_options = STRING_LIST_INIT_NODUP;\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n@@ -1137,10 +1169,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t   &options.allow_empty_message,\n \t\t\t   N_(\"allow rebasing commits with empty messages\"),\n \t\t\t   PARSE_OPT_HIDDEN),\n-\t\t{OPTION_STRING, 'r', \"rebase-merges\", &rebase_merges,\n-\t\t\tN_(\"mode\"),\n+\t\tOPT_CALLBACK_F('r', \"rebase-merges\", &options, N_(\"mode\"),\n \t\t\tN_(\"try to rebase merges instead of skipping them\"),\n-\t\t\tPARSE_OPT_OPTARG, NULL, (intptr_t)\"no-rebase-cousins\"},\n+\t\t\tPARSE_OPT_OPTARG, parse_opt_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n \t\tOPT_STRING('s', \"strategy\", &options.strategy,\n@@ -1436,14 +1467,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (options.exec.nr)\n \t\timply_merge(&options, \"--exec\");\n \n-\tif (rebase_merges) {\n-\t\tif (!strcmp(\"rebase-cousins\", rebase_merges))\n-\t\t\toptions.rebase_cousins = 1;\n-\t\telse if (strcmp(\"no-rebase-cousins\", rebase_merges))\n-\t\t\tdie(_(\"Unknown mode: %s\"), rebase_merges);\n-\t\toptions.rebase_merges = 1;\n+\tif (options.rebase_merges)\n \t\timply_merge(&options, \"--rebase-merges\");\n-\t}\n \n \tif (options.type == REBASE_APPLY) {\n \t\tif (ignore_whitespace)\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex b8ba323dbc..88d347cea5 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -299,6 +299,102 @@ test_expect_success '--rebase-merges=\"\" is invalid syntax' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'rebase.merges=\"\" is equivalent to not passing --rebase-merges' '\n+\ttest_config rebase.merges \"\" &&\n+\tgit checkout -b config-merges-blank E &&\n+\tgit rebase C &&\n+\ttest_cmp_graph C.. <<-\\EOF\n+\t* B\n+\t* D\n+\to C\n+\tEOF\n+'\n+\n+test_expect_success 'rebase.merges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '\n+\ttest_config rebase.merges rebase-cousins &&\n+\tgit checkout -b config-rebase-cousins main &&\n+\tgit rebase HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n+test_expect_success '--no-rebase-merges overrides rebase.merges=no-rebase-cousins' '\n+\ttest_config rebase.merges no-rebase-cousins &&\n+\tgit checkout -b override-config-no-rebase-cousins E &&\n+\tgit rebase --no-rebase-merges C &&\n+\ttest_cmp_graph C.. <<-\\EOF\n+\t* B\n+\t* D\n+\to C\n+\tEOF\n+'\n+\n+test_expect_success '--rebase-merges=no-rebase-cousins overrides rebase.merges=rebase-cousins' '\n+\ttest_config rebase.merges rebase-cousins &&\n+\tgit checkout -b override-config-rebase-cousins main &&\n+\tgit rebase --rebase-merges=no-rebase-cousins HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\to | H\n+\t|/\n+\to A\n+\tEOF\n+'\n+\n+test_expect_success '--rebase-merges overrides rebase.merges=false' '\n+\ttest_config rebase.merges false &&\n+\tgit checkout -b override-config-merges-false E &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase --rebase-merges C &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n+test_expect_success '--rebase-merges does not override rebase.merges=rebase-cousins' '\n+\ttest_config rebase.merges rebase-cousins &&\n+\tgit checkout -b no-override-config-rebase-cousins main &&\n+\tgit rebase --rebase-merges HEAD^ &&\n+\ttest_cmp_graph HEAD^.. <<-\\EOF\n+\t*   Merge the topic branch '\\''onebranch'\\''\n+\t|\\\n+\t| * D\n+\t| * G\n+\t|/\n+\to H\n+\tEOF\n+'\n+\n+test_expect_success 'local rebase.merges=false overrides global rebase.merges=true' '\n+\ttest_config_global rebase.merges true &&\n+\ttest_config rebase.merges false &&\n+\tgit checkout -b override-global-config E &&\n+\tgit rebase C &&\n+\ttest_cmp_graph C.. <<-\\EOF\n+\t* B\n+\t* D\n+\to C\n+\tEOF\n+'\n+\n+test_expect_success 'local rebase.merges=\"\" does not override global rebase.merges=true' '\n+\ttest_config_global rebase.merges no-rebase-cousins &&\n+\ttest_config rebase.merges \"\" &&\n+\tgit checkout -b no-override-global-config E &&\n+\tbefore=\"$(git rev-parse --verify HEAD)\" &&\n+\ttest_tick &&\n+\tgit rebase C &&\n+\ttest_cmp_rev HEAD $before\n+'\n+\n test_expect_success 'refs/rewritten/* is worktree-local' '\n \tgit worktree add wt &&\n \tcat >wt/script-from-scratch <<-\\EOF &&\n-- \n2.39.2\n\n"},{"id":"472467","messageId":"xmqqlekpb6f3.fsf@gitster.g","threadId":"59286","inReplyTo":"20230222051709.464275-2-alexhenrie24@gmail.com","subject":"Re: [PATCH v3 2/3] rebase: stop accepting --rebase-merges=\"\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-02-22T23:56:48Z","receivedAt":"2023-02-22T23:56:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alex Henrie <alexhenrie24@gmail.com> writes:\n\n> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n> index e0d910c229..b8ba323dbc 100755\n> --- a/t/t3430-rebase-merges.sh\n> +++ b/t/t3430-rebase-merges.sh\n> @@ -293,6 +293,12 @@ test_expect_success 'do not rebase cousins unless asked for' '\n>  \tEOF\n>  '\n>  \n> +test_expect_success '--rebase-merges=\"\" is invalid syntax' '\n> +\techo \"fatal: Unknown mode: \" >expect &&\n> +\t! git rebase --rebase-merges=\"\" HEAD^ 2>actual &&\n\n\"!\" takes any non-zero exit, even a segfaulting \"git\".  Let's use\ntest_must_fail to make sure it dies in a controlled way, i.e.\n\n\ttest_must_fail git rebase --rebase-merges=\"\" HEAD^ 2>actual &&\n\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'refs/rewritten/* is worktree-local' '\n>  \tgit worktree add wt &&\n>  \tcat >wt/script-from-scratch <<-\\EOF &&\n\nOther than that, looking good.\n\nThanks.\n"},{"id":"472468","messageId":"xmqqh6vdb5pm.fsf@gitster.g","threadId":"59286","inReplyTo":"20230222051709.464275-3-alexhenrie24@gmail.com","subject":"Re: [PATCH v3 3/3] rebase: add a config option for --rebase-merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-02-23T00:12:05Z","receivedAt":"2023-02-23T00:12:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alex Henrie <alexhenrie24@gmail.com> writes:\n\n> +rebase.merges::\n> +\tWhether and how to set the `--rebase-merges` option by default. Can\n> +\tbe `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to\n> +\ttrue is equivalent to `--rebase-merges` without an argument, setting to\n> +\t`rebase-cousins` or `no-rebase-cousins` is equivalent to\n> +\t`--rebase-merges` with that value as its argument, and setting to false\n> +\tis equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the\n> +\tcommand line without an argument overrides a `rebase.merges=false`\n> +\tconfiguration but does not override other values of `rebase.merge`.\n\nOK.  And ...\n\n> +static void parse_merges_value(struct rebase_options *options, const char *value)\n> +{\n> +\tif (value) {\n> +\t\tif (!strcmp(\"no-rebase-cousins\", value))\n> +\t\t\toptions->rebase_cousins = 0;\n> +\t\telse if (!strcmp(\"rebase-cousins\", value))\n> +\t\t\toptions->rebase_cousins = 1;\n> +\t\telse\n> +\t\t\tdie(_(\"Unknown mode: %s\"), value);\n> +\t}\n> +\n> +\toptions->rebase_merges = 1;\n> +}\n\n... this fallback parsing that is called after parse_maybe_bool()\nfinds the value a non-Boolean ...\n\n> @@ -815,6 +829,13 @@ static int rebase_config(const char *var, const char *value, void *data)\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (!strcmp(var, \"rebase.merges\") && value && *value) {\n> +\t\topts->rebase_merges = git_parse_maybe_bool(value);\n> +\t\tif (opts->rebase_merges < 0)\n> +\t\t\tparse_merges_value(opts, value);\n> +\t\treturn 0;\n> +\t}\n\n... looks pretty much standard way to handle such an \"extended\nBoolean\" value.  Nicely done.\n"},{"id":"472477","messageId":"CAMMLpeTsg6kRxun84YS_PwBkdUHPV=6St1KCavz-zcw+kZWzUQ@mail.gmail.com","threadId":"59286","inReplyTo":"xmqqlekpb6f3.fsf@gitster.g","subject":"Re: [PATCH v3 2/3] rebase: stop accepting --rebase-merges=\"\"","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2023-02-23T05:35:39Z","receivedAt":"2023-02-23T05:35:57Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"On Wed, Feb 22, 2023 at 4:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Alex Henrie <alexhenrie24@gmail.com> writes:\n>\n> > +test_expect_success '--rebase-merges=\"\" is invalid syntax' '\n> > +     echo \"fatal: Unknown mode: \" >expect &&\n> > +     ! git rebase --rebase-merges=\"\" HEAD^ 2>actual &&\n>\n> \"!\" takes any non-zero exit, even a segfaulting \"git\".  Let's use\n> test_must_fail to make sure it dies in a controlled way, i.e.\n>\n>         test_must_fail git rebase --rebase-merges=\"\" HEAD^ 2>actual &&\n\nDone in v4. Thanks for the feedback.\n\n-Alex\n"}]}