{"thread":{"id":"52496","subject":"[PATCH 00/15] rebase: make the default backend configurable","startedAt":"2019-12-20T17:09:53Z","lastAt":"2020-03-17T04:45:25Z","messageCount":161,"participants":["Elijah Newren via GitGitGadget","Alban Gruin","Elijah Newren","Junio C Hamano","SZEDER Gábor","Denton Liu","Phillip Wood","Johannes Schindelin","Jonathan Nieder","Emily Shaffer","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":15},"messages":[{"id":"388617","messageId":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":null,"subject":"[PATCH 00/15] rebase: make the default backend configurable","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:33Z","receivedAt":"2019-12-20T17:09:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series does a lot of work around making the default rebase backend\nconfigurable:\n\n * provide configurability of handling empty commits and iron out\n   differences between backends\n * increase/fix capabilities of the merge/interactive backend to make it\n   workable as the default\n * document the remaining differences in backends more thoroughly\n * add an --am option for explicitly requesting the am-backend\n * extend merge/interactive backend testing to put it on par with the am\n   backend\n * add a 'rebase.backend' config option and:\n * switch the rebase.backend default value from 'am' to 'merge'.\n\nAreas I'd like reviewers to focus, in priority order:\n\n * Patch 15 (Are we ready to switch the default backend? Or should we leave\n   this patch out for now? Junio suggested we may be ready or at least are\n   close[1])\n * Patch 1 (Does my empty handling make sense? Do others agree I fixed \n   --keep-empty, or do they view it as breaking it?)\n * Patch 8 (Do the updates to the documentation of behavioral differences\n   make sense? Is it too long?) * Patch 11 (okay to change the\n      git-completion shell prompt slightly, especially in light of patches\n      15 & 16? We did a prompt change previously when we merged the merge\n      backend with the interactive one, so I assume so, but just want to\n      make sure people have a chance to chime in.)\n   \n   \n\nIf it's too soon to switch from the 'am' to 'merge' backend, we can just\ndrop the last patch and I'll resubmit it later. I generated this series\nmostly through switching the default first and then watching what broke, but\nmoved the patch to the end to make it easy to drop.\n\nBriefly, reasons for switching the default backend boil down to the fact\nthat the am-backend drops information and thus limits what it can do. This\nmanifests in different ways:\n\n * lack of tree information that would allow us to warn users that new files\n   in old directories might want to move along with the other files that\n   were renamed with those directories[1]\n * incorrect application of patches in the presence of non-unique context\n   lines[2], which could be avoided with access to the original files\n   involved.\n * less information available to annotate conflict markers (since am creates\n   fake ancestors and commits on top of them, and doesn't have access to the\n   original commits)\n\n[1] https://lore.kernel.org/git/xmqqa78d2qmk.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[3] \nhttps://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n\nElijah Newren (15):\n  rebase: extend the options for handling of empty commits\n  t3406: simplify an already simple test\n  rebase, sequencer: remove the broken GIT_QUIET handling\n  rebase: make sure to pass along the quiet flag to the sequencer\n  rebase: fix handling of restrict_revision\n  t3432: make these tests work with either am or merge backends\n  rebase: allow more types of rebases to fast-forward\n  git-rebase.txt: add more details about behavioral differences of\n    backends\n  rebase: move incompatibility checks between backend options a bit\n    earlier\n  rebase: add an --am option\n  contrib: change the prompt for am-based rebases\n  rebase tests: mark tests specific to the am-backend with --am\n  rebase tests: repeat some tests using the merge backend instead of am\n  rebase: make the backend configurable via config setting\n  rebase: change the default backend from \"am\" to \"merge\"\n\n Documentation/config/rebase.txt         |   8 ++\n Documentation/git-rebase.txt            | 150 ++++++++++++++++----\n builtin/rebase.c                        | 181 +++++++++++++++++++-----\n contrib/completion/git-prompt.sh        |   2 +-\n rebase-interactive.c                    |   4 +-\n rebase-interactive.h                    |   2 +-\n sequencer.c                             |  80 ++++++++---\n sequencer.h                             |   6 +-\n t/t3400-rebase.sh                       |  36 ++++-\n t/t3401-rebase-and-am-rename.sh         |   4 +-\n t/t3404-rebase-interactive.sh           |   2 +-\n t/t3406-rebase-message.sh               |  19 ++-\n t/t3407-rebase-abort.sh                 |   6 +-\n t/t3420-rebase-autostash.sh             |   2 +-\n t/t3421-rebase-topology-linear.sh       |   4 +-\n t/t3424-rebase-empty.sh                 |  89 ++++++++++++\n t/t3425-rebase-topology-merges.sh       |   8 +-\n t/t3427-rebase-subtree.sh               |  16 ++-\n t/t3432-rebase-fast-forward.sh          |  59 ++++----\n t/t3433-rebase-options-compatibility.sh |  13 +-\n t/t5407-post-rewrite-hook.sh            |  12 +-\n t/t5520-pull.sh                         |  27 +++-\n t/t6047-diff3-conflict-markers.sh       |  13 +-\n t/t7512-status-help.sh                  |  12 +-\n t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-\n t/t9903-bash-prompt.sh                  |   6 +-\n 26 files changed, 582 insertions(+), 182 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\n\nbase-commit: 12029dc57db23baef008e77db1909367599210ee\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1\nPull-Request: https://github.com/git/git/pull/679\n-- \ngitgitgadget\n"},{"id":"388618","messageId":"47ea99fb30190b96b05f90735335424f7a37a95e.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 02/15] t3406: simplify an already simple test","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:35Z","receivedAt":"2019-12-20T17:09:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen the merge backend was re-implemented on top of the interactive\nbackend, the output of rebase --merge changed a little.  This change\nallowed this test to be simplified, though it wasn't noticed until now.\nSimplify the testcase a little.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3406-rebase-message.sh | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex b393e1e9fe..0c2c569f95 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -18,11 +18,8 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'rebase -m' '\n-\tgit rebase -m master >report &&\n-\t>expect &&\n-\tsed -n -e \"/^Already applied: /p\" \\\n-\t\t-e \"/^Committed: /p\" report >actual &&\n-\ttest_cmp expect actual\n+\tgit rebase -m master >actual &&\n+\ttest_must_be_empty actual\n '\n \n test_expect_success 'rebase against master twice' '\n-- \ngitgitgadget\n\n"},{"id":"388619","messageId":"b99feebae111814be75dd99b5547539f8a764339.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:36Z","receivedAt":"2019-12-20T17:09:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe GIT_QUIET environment variable was used to signal the non-am\nbackends that the rebase should perform quietly.  The preserve-merges\nbackend does not make use of the quiet flag anywhere (other than to\nwrite out its state whenever it writes state), and this mechanism was\nbroken in the conversion from shell to C.  Since this environment\nvariable was specifically designed for scripts and the only backend that\nwould still use it is no longer a script, just gut this code.\n\nA subsequent commit will fix --quiet for the interactive/merge backend\nin a different way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 6 ++----\n sequencer.c      | 6 ++----\n 2 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 96db10eaed..c71d169688 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t   opts->onto ? oid_to_hex(&opts->onto->object.oid) : \"\");\n \twrite_file(state_dir_path(\"orig-head\", opts), \"%s\",\n \t\t   oid_to_hex(&opts->orig_head));\n-\twrite_file(state_dir_path(\"quiet\", opts), \"%s\",\n-\t\t   opts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n+\tif (!(opts->flags & REBASE_NO_QUIET))\n+\t\twrite_file(state_dir_path(\"quiet\", opts), \"%s\", \"\");\n \tif (opts->flags & REBASE_VERBOSE)\n \t\twrite_file(state_dir_path(\"verbose\", opts), \"%s\", \"\");\n \tif (opts->strategy)\n@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"revisions\", opts->revisions);\n \tadd_var(&script_snippet, \"restrict_revision\", opts->restrict_revision ?\n \t\toid_to_hex(&opts->restrict_revision->object.oid) : NULL);\n-\tadd_var(&script_snippet, \"GIT_QUIET\",\n-\t\topts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n \tsq_quote_argv_pretty(&buf, opts->git_am_opts.argv);\n \tadd_var(&script_snippet, \"git_am_opt\", buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/sequencer.c b/sequencer.c\nindex d2c11f34b7..71062212a5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)\n int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\t      struct commit *onto, const char *orig_head)\n {\n-\tconst char *quiet = getenv(\"GIT_QUIET\");\n-\n \tif (head_name)\n \t\twrite_file(rebase_path_head_name(), \"%s\\n\", head_name);\n \tif (onto)\n@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \tif (orig_head)\n \t\twrite_file(rebase_path_orig_head(), \"%s\\n\", orig_head);\n \n-\tif (quiet)\n-\t\twrite_file(rebase_path_quiet(), \"%s\\n\", quiet);\n+\tif (opts->quiet)\n+\t\twrite_file(rebase_path_quiet(), \"%s\", \"\");\n \tif (opts->verbose)\n \t\twrite_file(rebase_path_verbose(), \"%s\", \"\");\n \tif (opts->strategy)\n-- \ngitgitgadget\n\n"},{"id":"388620","messageId":"13e2056e780b00baf86d4020c0974b6b05ce115b.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:34Z","receivedAt":"2019-12-20T17:09:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nExtend the interactive machinery with the ability to handle the full\nspread of options for how to handle commits that either start or become\nempty (by \"become empty\" I mean the changes in a commit are a subset of\nchanges that exist upstream, so the net effect of applying the commit is\nno changes).  Introduce a new command line flag for selecting the\ndesired behavior:\n    --empty={drop,keep,ask}\nwith the definitions:\n    drop: drop empty commits\n    keep: keep empty commits\n    ask:  provide the user a chance to interact and pick what to do with\n          empty commits on a case-by-case basis\n\nNote that traditionally, am-based rebases have always dropped commits\nthat either started or became empty, while interactive-based rebases\nhave defaulted to ask (and provided an option to keep commits that\nstarted empty).  This difference made sense since users of an am-based\nrebase just wanted to quickly batch apply a sequence of commits, while\nusers editing a todo list will likely want the chance to interact and\nhandle unusual cases on a case-by-case basis.  However, not all rebases\nusing the interactive machinery are explicitly interactive anymore.  In\nparticular --merge was always meant to behave more like --am: just\nrebase a batch of commits without popping up a todo list.\n\nIf the --empty flag is not specified, pick defaults as follows:\n    explicitly interactive: ask\n    --exec: keep (exec is about checking existing commits, and often\n                  used without actually changing the base.  Thus the\n                  expectation is that the user doesn't necessarily want\n                  anything to change; they just want to test).\n    otherwise: drop\n\nAlso, this commit makes --keep-empty just imply --empty=keep, and hides\nit from help so that we aren't confusing users with different ways to do\nthe same thing.  (I could have added a --drop-empty flag, but then that\ninvites users to specify both --keep-empty and --drop-empty and we have\nto add sanity checking around that; it seems cleaner to have a single\nmulti-valued option.)  This actually fixes --keep-empty too; previously,\nit only meant to sometimes keep empty commits, in particular commits\nwhich started empty would be kept.  But it would still error out and ask\nthe user what to do with commits that became empty.  Now it keeps empty\ncommits, as instructed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt      | 35 ++++++------\n builtin/rebase.c                  | 87 +++++++++++++++++++++++++++---\n rebase-interactive.c              |  4 +-\n rebase-interactive.h              |  2 +-\n sequencer.c                       | 74 +++++++++++++++++++------\n sequencer.h                       |  6 ++-\n t/t3421-rebase-topology-linear.sh |  4 +-\n t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++\n t/t3427-rebase-subtree.sh         | 16 +++---\n 9 files changed, 267 insertions(+), 50 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d0e2d27cc..ff32ca1080 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--empty={drop,keep,ask}::\n+\tHow to handle commits that become empty (because they contain a\n+\tsubset of already upstream changes) or start empty.  With drop\n+\t(the default), commits that start or become empty are dropped.\n+\tWith keep (implied by --exec), such commits are kept.  With ask\n+\t(implied by --interactive), the rebase will halt when an empty\n+\tcommit is applied allowing you to choose whether to drop it or\n+\tcommit it.  Also with ask, if the rebase is interactive then\n+\tcommits which start empty will be commented out in the todo\n+\taction list (giving you a chance to uncomment).\n++\n+Note that this has no effect on commits which are already upstream (as\n+can be checked via `git log --cherry-mark ...`), which are always\n+dropped by rebase.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --keep-empty::\n-\tKeep the commits that do not change anything from its\n-\tparents in the result.\n+\tDeprecated alias for what is now known as --empty=keep.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -569,6 +585,7 @@ are incompatible with the following options:\n  * --interactive\n  * --exec\n  * --keep-empty\n+ * --empty=\n  * --edit-todo\n  * --root when used in combination with --onto\n \n@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --ignore-whitespace\n  * --preserve-merges and --committer-date-is-author-date\n  * --preserve-merges and --ignore-date\n+ * --preserve-merges and --empty=\n  * --keep-base and --onto\n  * --keep-base and --root\n \n@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES\n \n There are some subtle differences how the backends behave.\n \n-Empty commits\n-~~~~~~~~~~~~~\n-\n-The am backend drops any \"empty\" commits, regardless of whether the\n-commit started empty (had no changes relative to its parent to\n-start with) or ended empty (all changes were already applied\n-upstream in other commits).\n-\n-The interactive backend drops commits by default that\n-started empty and halts if it hits a commit that ended up empty.\n-The `--keep-empty` option exists for the interactive backend to allow\n-it to keep commits that started empty.\n-\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ddf33bc9d4..96db10eaed 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -50,8 +50,16 @@ enum rebase_type {\n \tREBASE_PRESERVE_MERGES\n };\n \n+enum empty_type {\n+\tEMPTY_UNSPECIFIED = -1,\n+\tEMPTY_DROP,\n+\tEMPTY_KEEP,\n+\tEMPTY_ASK\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n+\tenum empty_type empty;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -77,7 +85,6 @@ struct rebase_options {\n \tconst char *action;\n \tint signoff;\n \tint allow_rerere_autoupdate;\n-\tint keep_empty;\n \tint autosquash;\n \tint ignore_whitespace;\n \tchar *gpg_sign_opt;\n@@ -95,6 +102,7 @@ struct rebase_options {\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n \treplay.allow_empty = 1;\n \treplay.allow_empty_message = opts->allow_empty_message;\n+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n+\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n+\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \n \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n \n-\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_ASK &&\n+\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n+\t\t\tTODO_LIST_ASK_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \treturn ret;\n }\n \n+static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->empty = EMPTY_KEEP;\n+\topts->type = REBASE_INTERACTIVE;\n+\treturn 0;\n+}\n+\n static const char * const builtin_rebase_interactive_usage[] = {\n \tN_(\"git rebase--interactive [<options>]\"),\n \tNULL\n@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n-\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\t N_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\topts->allow_rerere_autoupdate ?\n \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n-\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n+\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n@@ -1360,6 +1391,33 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static long parse_empty_value(const char *value)\n+{\n+\tif (!value)\n+\t\treturn EMPTY_UNSPECIFIED;\n+\telse if (!strcasecmp(value, \"drop\"))\n+\t\treturn EMPTY_DROP;\n+\telse if (!strcasecmp(value, \"keep\"))\n+\t\treturn EMPTY_KEEP;\n+\telse if (!strcasecmp(value, \"ask\"))\n+\t\treturn EMPTY_ASK;\n+\treturn EMPTY_UNSPECIFIED;\n+}\n+\n+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\tlong value = parse_empty_value(arg);\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tif (value < 0)\n+\t\treturn error(_(\"option empty accepts \\\"drop\\\", \"\n+\t\t\t       \"\\\"keep\\\", and \\\"ask\\\"\"));\n+\n+\toptions->empty = value;\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@@ -1505,8 +1563,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n-\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n-\t\t\t N_(\"preserve empty commits during rebase\")),\n+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n+\t\t\t       N_(\"how to handle empty commits\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n@@ -1770,8 +1833,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n-\tif (options.keep_empty)\n-\t\timply_interactive(&options, \"--keep-empty\");\n+\tif (options.empty != EMPTY_UNSPECIFIED)\n+\t\timply_interactive(&options, \"--empty\");\n \n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n@@ -1856,6 +1919,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \t}\n \n+\tif (options.empty == EMPTY_UNSPECIFIED) {\n+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n+\t\t\toptions.empty = EMPTY_ASK;\n+\t\telse if (exec.nr > 0)\n+\t\t\toptions.empty = EMPTY_KEEP;\n+\t\telse\n+\t\t\toptions.empty = EMPTY_DROP;\n+\t}\n \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad82bf77df 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n \treturn MISSING_COMMIT_CHECK_IGNORE;\n }\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf)\n {\n@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,\n \n \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \n-\tif (!keep_empty) {\n+\tif (!no_ask_empty) {\n \t\tmsg = _(\"Note that empty commits are commented out\");\n \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \t}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..f531e00ba7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -5,7 +5,7 @@ struct strbuf;\n struct repository;\n struct todo_list;\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf);\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/sequencer.c b/sequencer.c\nindex 763ccbbc45..d2c11f34b7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n \n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,\n \tempty_commit = is_original_commit_empty(commit);\n \tif (empty_commit < 0)\n \t\treturn empty_commit;\n-\tif (!empty_commit)\n+\tif (!empty_commit || opts->ask_on_initially_empty)\n \t\treturn 0;\n \telse\n \t\treturn 1;\n@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,\n \tchar *author = NULL;\n \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n \tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res, unborn = 0, reword = 0, allow;\n+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n \n \tif (opts->no_commit) {\n \t\t/*\n@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,\n \t\tgoto leave;\n \t}\n \n-\tallow = allow_empty(r, opts, commit);\n-\tif (allow < 0) {\n-\t\tres = allow;\n-\t\tgoto leave;\n-\t} else if (allow)\n-\t\tflags |= ALLOW_EMPTY;\n-\tif (!opts->no_commit) {\n+\tdrop_commit = 0;\n+\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n+\t\tdrop_commit = 1;\n+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n+\t} else {\n+\t\tallow = allow_empty(r, opts, commit);\n+\t\tif (allow < 0) {\n+\t\t\tres = allow;\n+\t\t\tgoto leave;\n+\t\t} else if (allow) {\n+\t\t\tflags |= ALLOW_EMPTY;\n+\t\t}\n+\t}\n+\tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n \t\telse\n@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n+\t\topts->drop_redundant_commits =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n+\t\topts->ask_on_initially_empty =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n \t\t\topts->reschedule_failed_exec = 1;\n \n+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n+\t\t\topts->drop_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n+\t\t\topts->keep_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n+\t\t\topts->ask_on_initially_empty = 1;\n+\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n \tif (opts->ignore_date)\n \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n+\tif (opts->drop_redundant_commits)\n+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n+\tif (opts->keep_redundant_commits)\n+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n+\tif (opts->ask_on_initially_empty)\n+\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n \tif (opts->reschedule_failed_exec)\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \n@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n \tif (opts->allow_empty_message)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.allow-empty-message\", \"true\");\n+\tif (opts->drop_redundant_commits)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n \tif (opts->keep_redundant_commits)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n+\tif (opts->ask_on_initially_empty)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n \tif (opts->signoff)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\t\"options.signoff\", \"true\");\n@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n \t\t\t\t   unsigned flags)\n {\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tis_empty = is_original_commit_empty(commit);\n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n \n \t\tstrbuf_reset(&oneline);\n \t\tpretty_print_commit(pp, commit, &oneline);\n@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tif (!to_merge) {\n \t\t\t/* non-merge commit: easy case */\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tif (!keep_empty && is_empty)\n+\t\t\tif (is_empty && ask_empty)\n \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n \t\t\t\t    oid_to_hex(&commit->object.oid),\n@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \tstruct pretty_print_context pp = {0};\n \tstruct rev_info revs;\n \tstruct commit *commit;\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n \n \twhile ((commit = get_revision(&revs))) {\n-\t\tint is_empty  = is_original_commit_empty(commit);\n+\t\tint is_empty = is_original_commit_empty(commit);\n \n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n-\t\tif (!keep_empty && is_empty)\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n+\t\tif (is_empty && ask_empty)\n \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n \t\tstrbuf_addf(out, \"%s %s \", insn,\n \t\t\t    oid_to_hex(&commit->object.oid));\n@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \n \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n-\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n+\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n+\t\t\t\t count_commands(todo_list),\n \t\t\t\t shortrevisions, shortonto, &buf);\n \n \tres = write_message(buf.buf, buf.len, file, 0);\ndiff --git a/sequencer.h b/sequencer.h\nindex e9a0e03ea2..1c3abb661c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -39,7 +39,9 @@ struct replay_opts {\n \tint allow_rerere_auto;\n \tint allow_empty;\n \tint allow_empty_message;\n+\tint drop_redundant_commits;\n \tint keep_redundant_commits;\n+\tint ask_on_initially_empty;\n \tint verbose;\n \tint quiet;\n \tint reschedule_failed_exec;\n@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-#define TODO_LIST_KEEP_EMPTY (1U << 0)\n+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);\n  * `--onto`, we do not want to re-generate the root commits.\n  */\n #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n+#define TODO_LIST_DROP_EMPTY (1U << 7)\n+#define TODO_LIST_ASK_EMPTY (1U << 8)\n \n \n int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 325072b0a3..d23e0bf778 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -230,7 +230,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -245,7 +245,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n test_run_rebase success --rebase-merges\n \n #       m\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nnew file mode 100755\nindex 0000000000..9d52e1417f\n--- /dev/null\n+++ b/t/t3424-rebase-empty.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='git rebase of commits that start or become empty'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup test repository' '\n+\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n+\ttest_write_lines A B C D E F G H I J >letters &&\n+\tgit add numbers letters &&\n+\tgit commit -m A &&\n+\n+\tgit branch upstream &&\n+\tgit branch localmods &&\n+\n+\tgit checkout upstream &&\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m B &&\n+\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C &&\n+\n+\tgit checkout localmods &&\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C2 &&\n+\n+\tgit commit --allow-empty -m D &&\n+\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m \"Five letters ought to be enough for anybody\"\n+'\n+\n+test_expect_success 'rebase --merge --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n+\n+\ttest_must_fail git rebase --skip &&\n+\tgit commit --allow-empty &&\n+\tgit rebase --continue &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n+\n+test_expect_success 'rebase --interactive --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_done\ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex bec48e6a1f..468ebc1bef 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n-- \ngitgitgadget\n\n"},{"id":"388621","messageId":"9b1ad46c58b7f8397acbf2a8339e150dfb04c956.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 06/15] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:39Z","receivedAt":"2019-12-20T17:09:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3432 had several stress tests for can_fast_forward(), whose intent was\nto ensure we were using the optimization of just fast forwarding when\npossible.  However, these tests verified that fast forwards had happened\nbased on the output that rebase printed to the terminal.  We can instead\ntest more directly that we actually fast-forwarded by checking the\nreflog, which also has the side effect of making the tests applicable\nfor the merge/interactive backend.\n\nThis change does lose the distinction between \"noop\" and \"noop-force\",\nbut as stated in commit c9efc216830f (\"t3432: test for --no-ff's\ninteraction with fast-forward\", 2019-08-27) which introduced that\ndistinction: \"These tests aren't supposed to endorse the status quo,\njust test for what we're currently doing.\".\n\nThis change does not actually run these tests with the merge/interactive\nbackend; instead this is just a preparatory commit.  A subsequent commit\nwhich fixes can_fast_forward() to work with that backend will then also\nchange t3432 to add tests of that backend as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 53 ++++++++++++++++------------------\n 1 file changed, 25 insertions(+), 28 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 92f95b57da..3879a43fa0 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,19 +44,16 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tcp .git/logs/HEAD expect &&\n \t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n-\t\t\t# Must check this case first, for 'is up to\n-\t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\twc -l .git/logs/HEAD >old &&\n+\t\t\twc -l .git/logs/HEAD >new &&\n+\t\t\ttest_line_count '-gt' $(($old + 2)) .git/logs/HEAD\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout &&\n-\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n-\t\telif test $what = noop-force\n-\t\tthen\n-\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n+\t\t\ttest_cmp expect .git/logs/HEAD\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -71,14 +68,14 @@ test_rebase_same_head_ () {\n \n changes='no changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -91,14 +88,14 @@ test_expect_success 'add work same to side' '\n \n changes='our changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -112,13 +109,13 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head success noop same success work diff --keep-base master\n test_rebase_same_head success noop same success work diff --keep-base\n-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work diff --fork-point --onto B B\n+test_rebase_same_head success work same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n \n-- \ngitgitgadget\n\n"},{"id":"388623","messageId":"66bee10d1ca45548cf9b5a5bc2e17703ed54a82f.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 04/15] rebase: make sure to pass along the quiet flag to the sequencer","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:37Z","receivedAt":"2019-12-20T17:09:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  | 3 ++-\n t/t3400-rebase.sh | 8 +++++++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex c71d169688..cc8f3f008f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n \treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n \t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n+\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -1506,7 +1507,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n \t\t\t   N_(\"be quiet. implies --no-stat\"),\n-\t\t\t   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),\n+\t\t\t   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\n \t\tOPT_BIT('v', \"verbose\", &options.flags,\n \t\t\tN_(\"display a diffstat of what changed upstream\"),\n \t\t\tREBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 221b35f2df..79762b989a 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase -q is quiet' '\n+test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n \tgit rebase -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n+test_expect_success 'rebase --merge -q is quiet' '\n+\tgit checkout -B quiet topic &&\n+\tgit rebase --merge -q master >output.out 2>&1 &&\n+\ttest_must_be_empty output.out\n+'\n+\n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n \t\techo \"One\" &&\n-- \ngitgitgadget\n\n"},{"id":"388625","messageId":"5c31d664f3b410cc165ec7757041a0a82ba2b8c0.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 09/15] rebase: move incompatibility checks between backend options a bit earlier","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:42Z","receivedAt":"2019-12-20T17:10:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 67bccd876f..9e7e88b147 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1910,6 +1910,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n+\tif (options.git_am_opts.argc) {\n+\t\t/* all am options except -q are compatible only with --am */\n+\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n+\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n+\t\t\t\tbreak;\n+\n+\t\tif (is_interactive(&options) && i >= 0)\n+\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t      \"interactive or merge options\"));\n+\t}\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_INTERACTIVE:\n@@ -1940,17 +1951,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (reschedule_failed_exec >= 0)\n \t\toptions.reschedule_failed_exec = reschedule_failed_exec;\n \n-\tif (options.git_am_opts.argc) {\n-\t\t/* all am options except -q are compatible only with --am */\n-\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n-\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-\t\t\t\tbreak;\n-\n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n-\t}\n-\n \tif (options.signoff) {\n \t\tif (options.type == REBASE_PRESERVE_MERGES)\n \t\t\tdie(\"cannot combine '--signoff' with \"\n-- \ngitgitgadget\n\n"},{"id":"388622","messageId":"a4073df9108b7e9a825371f5925d1841463aedb4.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 07/15] rebase: allow more types of rebases to fast-forward","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:40Z","receivedAt":"2019-12-20T17:10:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we dis-allowed rebases using the interactive backend from\nperforming a fast-forward to short-circuit the rebase operation.  This\nmade sense for explicitly interactive rebases and some implicitly\ninteractive rebases, but certainly became overly stringent when the\nmerge backend was re-implemented via the interactive backend.\n\nJust as the am-based rebase has always had to disable the fast-forward\nbased on a variety of conditions or flags (e.g. --signoff, --whitespace,\netc.), we need to do the same but now with a few more options.  However,\ncontinuing to use REBASE_FORCE for tracking this is problematic because\nthe interactive backend used it for a different purpose.  (When\nREBASE_FORCE wasn't set, the interactive backend would not fast-forward\nthe whole series but would fast-forward individual \"pick\" commits at the\nbeginning of the todo list, and then a squash or something would cause\nit to start generating new commits.)  So, introduce a new\nallow_preemptive_ff flag contained within cmd_rebase() and use it to\ntrack whether we are going to allow a pre-emptive fast-forward that\nshort-circuits the whole rebase.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c               | 18 ++++++++++++++----\n t/t3432-rebase-fast-forward.sh |  2 ++\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b320bb3a30..67bccd876f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1497,6 +1497,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n+\tint allow_preemptive_ff = 1;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1808,11 +1809,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t    options.ignore_date)\n \t\toptions.flags |= REBASE_FORCE;\n \n+\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n+\t    (action != ACTION_NONE) ||\n+\t    (exec.nr > 0) ||\n+\t    options.autosquash) {\n+\t\tallow_preemptive_ff = 0;\n+\t}\n+\n \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n \t\tconst char *option = options.git_am_opts.argv[i], *p;\n \t\tif (!strcmp(option, \"--whitespace=fix\") ||\n \t\t    !strcmp(option, \"--whitespace=strip\"))\n-\t\t\toptions.flags |= REBASE_FORCE;\n+\t\t\tallow_preemptive_ff = 0;\n \t\telse if (skip_prefix(option, \"-C\", &p)) {\n \t\t\twhile (*p)\n \t\t\t\tif (!isdigit(*(p++)))\n@@ -2148,12 +2156,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t/*\n \t * Check if we are already based on onto with linear history,\n \t * in which case we could fast-forward without replacing the commits\n-\t * with new commits recreated by replaying their changes. This\n-\t * optimization must not be done if this is an interactive rebase.\n+\t * with new commits recreated by replaying their changes.\n+\t *\n+\t * Note that can_fast_forward() initializes merge_base, so we have to\n+\t * call it before checking allow_preemptive_ff.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\t    allow_preemptive_ff) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 3879a43fa0..58c91c6899 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -30,6 +30,8 @@ test_rebase_same_head () {\n \tshift &&\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\n \n test_rebase_same_head_ () {\n-- \ngitgitgadget\n\n"},{"id":"388624","messageId":"90e8927ea0533a078fe7fade9264f798bcdaa6ad.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 08/15] git-rebase.txt: add more details about behavioral differences of backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:41Z","receivedAt":"2019-12-20T17:10:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---\n t/t3433-rebase-options-compatibility.sh |   5 +-\n 2 files changed, 94 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex ff32ca1080..f1ace07c38 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used\n with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n-\tBehaves differently depending on which backend is selected.\n-+\n-'am' backend: When applying a patch, ignore changes in whitespace in\n-context lines if necessary.\n-+\n-'interactive' backend: Treat lines with only whitespace changes as\n-unchanged for the sake of a three-way merge.\n+\tIgnore whitespace-only changes in the commits being rebased,\n+\twhich may avoid \"unnecessary\" conflicts.  (Both backends\n+\tcurrently have differing edgecase bugs with this option; see\n+\tBEHAVIORAL DIFFERENCES.)\n \n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n-Directory rename heuristics are enabled in the merge and interactive\n-backends.  Due to the lack of accurate tree information, directory\n-rename detection is disabled in the am backend.\n+Due to the lack of accurate tree information (arising from\n+constructing fake ancestors with the limited information available in\n+patches), directory rename detection is disabled in the am backend.\n+Disabled directory rename detection means that if one side of history\n+renames a directory and the other adds new files to the old directory,\n+then the new files will be left behind in the old directory without\n+any warning at the time of rebasing that you may want to move these\n+files into the new directory.\n+\n+Directory rename detection works with the merge and interactive\n+backends to provide you warnings in such cases.\n+\n+Context\n+~~~~~~~\n+\n+The am backend works by creating a sequence of patches (by calling\n+`format-patch` internally), and then applying the patches in sequence\n+(calling `am` internally).  Patches are composed of multiple hunks,\n+each with line numbers, a context region, and the actual changes.  The\n+line numbers have to be taken with some fuzz, since the other side\n+will likely have inserted or deleted lines earlier in the file.  The\n+context region is meant to help find how to adjust the line numbers in\n+order to apply the changes to the right lines.  However, if multiple\n+areas of the code have the same surrounding lines of context, the\n+wrong one can be picked.  There are real-world cases where this has\n+caused commits to be reapplied incorrectly with no conflicts reported.\n+Setting diff.context to a larger value may prevent such types of\n+problems, but increases the chance of spurious conflicts (since it\n+will require more lines of matching context to apply).\n+\n+The interactive backend works with a full copy of each relevant file,\n+insulating it from these types of problems.\n+\n+Labelling of conflicts markers\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+When there are content conflicts, the merge machinery tries to\n+annotate each side's conflict markers with the commits where the\n+content came from.  Since the am backend drops the original\n+information about the rebased commits and their parents (and instead\n+generates new fake commits based off limited information in the\n+generated patches), those commits cannot be identified; instead it has\n+to fall back to a commit summary.  Also, when merge.conflictStyle is\n+set to diff3, the am backend will use \"constructed merge base\" to\n+label the content from the merge base, and thus provide no information\n+about the merge base commit whatsoever.\n+\n+The interactive backend works with the full commits on both sides of\n+history and thus has no such limitations.\n+\n+--ignore-whitespace\n+~~~~~~~~~~~~~~~~~~~\n+\n+The --ignore-whitespace option is supposed to ignore whitespace-only\n+changes if it allows the code to merge cleanly.  Unfortunately, the\n+different backends implement this differently, and both have different\n+edge case bugs.\n++\n+'am' backend: When applying a patch, ignore changes in whitespace in\n+context lines if necessary.  (Which implies that if the whitespace\n+change was not in the context lines but on a line with a real change,\n+then the rebase will still fail with \"unnecessary\" content conflicts.)\n++\n+'interactive' backend: Treat lines with only whitespace changes as\n+unchanged for the sake of a three-way merge.  This means that if one\n+side made no changes and the commits being rebased had whitespace-only\n+changes, those whitespaces fixups will be discarded despite the fact\n+that they present no content conflict.\n+\n+Miscellaneous differences\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+There are a few more behavioral differences that most folks would\n+probably consider inconsequential but which are mentioned for\n+completeness:\n+\n+* Reflog: The two backends will use different wording when describing\n+  the changes made in the reflog, though both will make use of the\n+  word \"rebase\".\n+\n+* Progress, informational, and error messages: The two backends\n+  provide slightly different progress and informational messages.\n+  Also, the am backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the interactive backend\n+  writes them to stderr.\n+\n+* State directories: The two backends keep their state in different\n+  directories under .git/\n \n include::merge-strategies.txt[]\n \ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex 5166f158dd..bd4d2d2f63 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe\n GIT_AUTHOR_DATE=\"1999-04-02T08:03:20+05:30\"\n export GIT_AUTHOR_DATE\n \n-# This is a special case in which both am and interactive backends\n-# provide the same output. It was done intentionally because\n-# both the backends fall short of optimal behaviour.\n+# This is a common case in which both am and interactive backends\n+# provide the same output with --ignore-whitespace.\n test_expect_success 'setup' '\n \tgit checkout -b topic &&\n \tq_to_tab >file <<-\\EOF &&\n-- \ngitgitgadget\n\n"},{"id":"388626","messageId":"81d0f18b3e296230b60fddb97720c708e35dce1a.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 13/15] rebase tests: repeat some tests using the merge backend instead of am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:46Z","receivedAt":"2019-12-20T17:10:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn order to ensure the merge/interactive backend gets similar coverage\nto the am one, add some tests for cases where previously only the am\nbackend was tested.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t5520-pull.sh                   | 17 +++++++++++++++--\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--\n 2 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 602d996a33..3fff6a06fa 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -277,14 +277,27 @@ test_expect_success '--rebase' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--rebase fast forward' '\n+test_expect_success '--rebase (merge) fast forward' '\n \tgit reset --hard before-rebase &&\n \tgit checkout -b ff &&\n \techo another modification >file &&\n \tgit commit -m third file &&\n \n \tgit checkout to-rebase &&\n-\tgit pull --rebase . ff &&\n+\tgit -c rebase.backend=merge pull --rebase . ff &&\n+\ttest_cmp_rev HEAD ff &&\n+\n+\t# The above only validates the result.  Did we actually bypass rebase?\n+\tgit reflog -1 >reflog.actual &&\n+\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\ttest_cmp reflog.expected reflog.fuzzy\n+'\n+\n+test_expect_success '--rebase (am) fast forward' '\n+\tgit reset --hard before-rebase &&\n+\n+\tgit -c rebase.backend=am pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 860542aad0..d383ce8130 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n-test_expect_success 'rebase describes fake ancestor base' '\n+test_expect_success 'rebase --merge describes parent of commit being picked' '\n \ttest_create_repo rebase &&\n \t(\n \t\tcd rebase &&\n@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '\n \t\ttest_commit master file &&\n \t\tgit checkout -b side HEAD^ &&\n \t\ttest_commit side file &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&\n+\t\tgrep \"||||||| parent of\" file\n+\t)\n+'\n+\n+test_expect_success 'rebase --am describes fake ancestor base' '\n+\t(\n+\t\tcd rebase &&\n+\t\tgit rebase --abort &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"388631","messageId":"bcac3b77e8aebe4f37afb94ff51da97c78f06e01.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 12/15] rebase tests: mark tests specific to the am-backend with --am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:45Z","receivedAt":"2019-12-20T17:10:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have many rebase tests in the testsuite, and often the same test is\nrepeated multiple times just testing different backends.  For those\ntests that were specifically trying to test the am backend, add the --am\nflag.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3400-rebase.sh                       | 10 +++++-----\n t/t3401-rebase-and-am-rename.sh         |  4 ++--\n t/t3404-rebase-interactive.sh           |  2 +-\n t/t3406-rebase-message.sh               | 12 ++++++------\n t/t3407-rebase-abort.sh                 |  6 +++---\n t/t3420-rebase-autostash.sh             |  2 +-\n t/t3425-rebase-topology-merges.sh       |  8 ++++----\n t/t3432-rebase-fast-forward.sh          |  4 ++--\n t/t3433-rebase-options-compatibility.sh |  8 ++++----\n t/t5407-post-rewrite-hook.sh            | 12 ++++++------\n t/t7512-status-help.sh                  | 12 ++++++------\n 11 files changed, 40 insertions(+), 40 deletions(-)\n\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 71fd6396cd..0a491f2363 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \n test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase -q master >output.out 2>&1 &&\n+\tgit rebase --am -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex a0b9438b22..50803958fd 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase (am): directory rename detected' '\n+test_expect_failure 'rebase --am: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase A &&\n+\t\tgit -c merge.directoryRenames=true rebase --am A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ae6e55ce79..743b7e511a 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 0c2c569f95..7ce617fc1f 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase master >out &&\n+\tgit rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase master >out &&\n+\tgit rebase --force-rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase master topic >out &&\n+\tgit rebase --am master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase topic >out &&\n+\tgit rebase --am topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase reflog-onto &&\n+\tgit rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 910f218284..3e31826170 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --quit' '\n+test_expect_success 'rebase --am --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase master &&\n+\ttest_must_fail git rebase --am master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 5f7e73cf83..3816159e20 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex fd8efe84fe..19700b025b 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' ''\n+test_run_rebase success 'n o e' --am\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 58c91c6899..b22aa8c7ee 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex bd4d2d2f63..a07e1f276b 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '\n \tnew line 2\n \tline 3\n \tEOF\n-\ttest_must_fail git rebase main side &&\n+\ttest_must_fail git rebase --am main side &&\n \tgit rebase --abort &&\n-\tgit rebase --ignore-whitespace main side &&\n+\tgit rebase --am --ignore-whitespace main side &&\n \ttest_cmp expect file\n '\n \n@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '\n \n test_expect_success '--committer-date-is-author-date works with am backend' '\n \tgit commit --amend &&\n-\tgit rebase --committer-date-is-author-date HEAD^ &&\n+\tgit rebase --am --committer-date-is-author-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgit show HEAD --pretty=\"format:%ci\" >committertime &&\n \ttest_cmp authortime committertime\n@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '\n # sets to +0530.\n test_expect_success '--ignore-date works with am backend' '\n \tgit commit --amend --date=\"$GIT_AUTHOR_DATE\" &&\n-\tgit rebase --ignore-date HEAD^ &&\n+\tgit rebase --am --ignore-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgrep \"+0000\" authortime\n '\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 7344253bfb..a8a73616e4 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase' '\n+test_expect_success 'git rebase --am' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip' '\n+test_expect_success 'git rebase --am --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip the last one' '\n+test_expect_success 'git rebase --am --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto D A &&\n+\ttest_must_fail git rebase --am --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 66d7a62797..d22b0acf2a 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase in progress before rebase --continue' '\n+test_expect_success 'status when rebase --am in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\n-- \ngitgitgadget\n\n"},{"id":"388632","messageId":"04db4472d36041f42766c00f84414ac9fb81d885.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 14/15] rebase: make the backend configurable via config setting","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:47Z","receivedAt":"2019-12-20T17:10:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt |  8 ++++++++\n builtin/rebase.c                | 31 ++++++++++++++++++++++++-------\n 2 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex d98e32d812..e6ae30c999 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -5,6 +5,14 @@ rebase.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+rebase.backend::\n+\tDefault backend to use for rebasing.  Possible choices are\n+\t'am' or 'merge' (note that the merge backend is sometimes also\n+\trefered to as the interactive backend or the interactive\n+\tmachinery elsewhere in the docs).  Also, in the future, if the\n+\tmerge backend gains all remaining capabilities of the am\n+\tbackend, this setting may become unused.\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ab9e16b206..eaa5eac59e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -60,6 +60,7 @@ enum empty_type {\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n+\tconst char *default_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -103,6 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n+\t\t.default_backend = \"am\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)\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+\n \treturn git_default_config(var, value, data);\n }\n \n@@ -1932,9 +1938,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n+\t\tif (i >= 0) {\n+\t\t\tif (is_interactive(&options))\n+\t\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t\t      \"interactive or merge options\"));\n+\t\t\telse\n+\t\t\t\toptions.type = REBASE_AM;\n+\t\t}\n+\t}\n+\n+\tif (options.type == REBASE_UNSPECIFIED) {\n+\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"am\"))\n+\t\t\toptions.type = REBASE_AM;\n+\t\telse\n+\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n+\t\t\t    options.default_backend);\n \t}\n \n \tswitch (options.type) {\n@@ -1947,10 +1967,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n-\t\t/* the default rebase backend is `--am` */\n-\t\toptions.type = REBASE_AM;\n-\t\toptions.state_dir = apply_dir();\n-\t\tbreak;\n+\t\tBUG(\"options.type was just set above; should be unreachable.\");\n \t}\n \n \tif (options.empty == EMPTY_UNSPECIFIED) {\n-- \ngitgitgadget\n\n"},{"id":"388630","messageId":"1c3f8ba328982e633e1431eba3fabcb230821ddc.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 11/15] contrib: change the prompt for am-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:44Z","receivedAt":"2019-12-20T17:10:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe prompt for am-based rebases was REBASE, while for interactive-based\nrebases was REBASE-i.  A while ago, we switched merge-based rebases from\nusing REBASE-m to REBASE-i via re-implementing the merge backend based\non the interactive backend.  We will soon be changing the default rebase\nbackend to the interactive one, meaning the default prompt will be\nREBASE-i rather than REBASE.  We have also noted in the documentation\nthat currently am-specific options will be implemented in the\ninteractive backend, and even the --am flag may eventually imply an\ninteractive-based rebase.  As such, change the prompt for an am-based\nrebase from REBASE to REBASE-a.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n contrib/completion/git-prompt.sh | 2 +-\n t/t9903-bash-prompt.sh           | 6 +++---\n 2 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\nindex 1d510cd47b..3c81099d60 100644\n--- a/contrib/completion/git-prompt.sh\n+++ b/contrib/completion/git-prompt.sh\n@@ -440,7 +440,7 @@ __git_ps1 ()\n \t\t\t__git_eread \"$g/rebase-apply/last\" total\n \t\t\tif [ -f \"$g/rebase-apply/rebasing\" ]; then\n \t\t\t\t__git_eread \"$g/rebase-apply/head-name\" b\n-\t\t\t\tr=\"|REBASE\"\n+\t\t\t\tr=\"|REBASE-a\"\n \t\t\telif [ -f \"$g/rebase-apply/applying\" ]; then\n \t\t\t\tr=\"|AM\"\n \t\t\telse\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 88bc733ad6..8da5b1aee2 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n \ttest_cmp expected \"$actual\"\n '\n \n-test_expect_success 'prompt - rebase' '\n-\tprintf \" (b2|REBASE 1/3)\" >expected &&\n+test_expect_success 'prompt - rebase am' '\n+\tprintf \" (b2|REBASE-a 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase b1 b2 &&\n+\ttest_must_fail git rebase --am b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n\n"},{"id":"388627","messageId":"b84faa86846cba6fb3f4bfa1d4f216fbce3867eb.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 05/15] rebase: fix handling of restrict_revision","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:38Z","receivedAt":"2019-12-20T17:10:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nrestrict_revision in the original shell script was an excluded revision\nrange.  It is also treated that way by the am-backend.  In the\nconversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\nimplement the logic to initialize $revisions in C\", 2018-08-28)), the\ninteractive-backend accidentally treated it as a positive revision\nrather than a negated one.\n\nThis was missed as there were no tests in the testsuite that tested an\ninteractive rebase with fork-point behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  |  4 ++--\n t/t3400-rebase.sh | 20 +++++++++++++++++++-\n 2 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex cc8f3f008f..b320bb3a30 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \n \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n \tif (opts->restrict_revision)\n-\t\targv_array_push(&make_script_args,\n-\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n+\t\targv_array_pushf(&make_script_args, \"^%s\",\n+\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n \n \tret = sequencer_make_script(the_repository, &todo_list.buf,\n \t\t\t\t    make_script_args.argc, make_script_args.argv,\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 79762b989a..71fd6396cd 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n \tgit rebase master\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n \tgit checkout -b default-base master &&\n \tgit checkout -b default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual &&\n+\tgit checkout default-base &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout default &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+\tgit checkout -B default-base master &&\n+\tgit checkout -B default topic &&\n+\tgit config branch.default.remote . &&\n+\tgit config branch.default.merge refs/heads/default-base &&\n \tgit rebase &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n-- \ngitgitgadget\n\n"},{"id":"388628","messageId":"999587933b2ce52db1687922b7d2b6a55049a7e0.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:48Z","receivedAt":"2019-12-20T17:10:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe am-backend drops information and thus limits what we can do:\n\n  * lack of full tree information from the original commits means we\n    cannot do directory rename detection and warn users that they might\n    want to move some of their new files that they placed in old\n    directories to prevent their becoming orphaned.[1]\n  * reduction in context from only having a few lines beyond those\n    changed means that when context lines are non-unique we can apply\n    patches incorrectly.[2]\n  * lack of access to original commits means that conflict marker\n    annotation has less information available.\n\nAlso, the merge/interactive backend have far more abilities, appear to\ncurrently have a slight performance advantage[3] and have room for more\noptimizations than the am backend[4] (and work is underway to take\nadvantage of some of those possibilities).\n\n[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt           |  2 +-\n builtin/rebase.c                       |  4 ++--\n t/t5520-pull.sh                        | 10 ++++++----\n t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n 4 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex cf1ac2e359..e819889a31 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.\n --merge::\n \tUse merging strategies to rebase.  When the recursive (default) merge\n \tstrategy is used, this allows rebase to be aware of renames on the\n-\tupstream side.\n+\tupstream side.  This is the default.\n +\n Note that a rebase merge works by replaying each commit from the working\n branch on top of the <upstream> branch.  Because of this, when a merge\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eaa5eac59e..1c3d0b97d5 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -104,7 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"am\",\t  \t\\\n+\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1949,7 +1949,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\toptions.type = REBASE_MERGE;\n+\t\t\timply_interactive(&options, \"--merge\");\n \t\telse if (!strcmp(options.default_backend, \"am\"))\n \t\t\toptions.type = REBASE_AM;\n \t\telse\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 3fff6a06fa..4f9e7f7ff6 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '\n \ttest_tick &&\n \tgit commit -m \"Create conflict\" seq.txt &&\n \ttest_must_fail git pull --rebase . seq 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success 'failed --rebase shows advice' '\n@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '\n \tgit checkout -f -b fails-to-rebase HEAD^ &&\n \ttest_commit v2-without-cr file \"2\" file2-lf &&\n \ttest_must_fail git pull --rebase . diverging 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success '--rebase fails with multiple branches' '\n@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull --rebase &&\n-\t\tfind .git/rebase-apply -name \"000*\" >patches &&\n-\t\ttest_line_count = 1 patches\n+\t\tcat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&\n+\t\tgrep -v -e \\# -e ^$ work >patches &&\n+\t\ttest_line_count = 1 patches &&\n+\t\trm -f work\n \t)\n '\n \ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex dbe8deac0d..aec45bca3b 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' \"\n \n \n test_expect_success 'check that rebase really failed' '\n-\ttest -d .git/rebase-apply\n+\tgit status >output &&\n+\tgrep currently.rebasing output\n '\n \n test_expect_success 'resolve, continue the rebase and dcommit' \"\n-- \ngitgitgadget\n"},{"id":"388629","messageId":"340bf71c5cd2086c638a652d1e111876a723fc6a.1576861788.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH 10/15] rebase: add an --am option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-20T17:09:43Z","receivedAt":"2019-12-20T17:10:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCurrently, this option doesn't do anything except error out if any\noptions requiring the interactive-backend are also passed.  However,\nwhen we make the default backend configurable later in this series, this\nflag will provide a way to override the config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 11 ++++++++++-\n builtin/rebase.c             | 18 +++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex f1ace07c38..cf1ac2e359 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--am:\n+\tUse git-am internally to rebase.  This option may become a\n+\tno-op in the future once the interactive backend handles\n+\teverything the am one does.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --empty={drop,keep,ask}::\n \tHow to handle commits that become empty (because they contain a\n \tsubset of already upstream changes) or start empty.  With drop\n@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.\n+\tever ignored.  Implies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n+\tImplies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n+ * --am\n  * --whitespace\n  * -C\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 9e7e88b147..ab9e16b206 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \treturn res && is_linear_history(onto, head);\n }\n \n+static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_AM;\n+\n+\treturn 0;\n+}\n+\n /* -i followed by -m is still -i */\n static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n {\n@@ -1550,6 +1562,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n+\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n+\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n \t\t\tN_(\"use merging strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n@@ -1910,7 +1926,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc) {\n+\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n \t\t/* all am options except -q are compatible only with --am */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-- \ngitgitgadget\n\n"},{"id":"388661","messageId":"0387d5b2-6b53-a428-291b-2489a9447bf3@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/15] rebase: make the default backend configurable","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-12-20T18:51:38Z","receivedAt":"2019-12-20T18:51:49Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Elijah,\n\nLe 20/12/2019 à 18:09, Elijah Newren via GitGitGadget a écrit :\n> This series does a lot of work around making the default rebase backend\n> configurable:\n> \n>  * provide configurability of handling empty commits and iron out\n>    differences between backends\n>  * increase/fix capabilities of the merge/interactive backend to make it\n>    workable as the default\n>  * document the remaining differences in backends more thoroughly\n>  * add an --am option for explicitly requesting the am-backend\n>  * extend merge/interactive backend testing to put it on par with the am\n>    backend\n>  * add a 'rebase.backend' config option and:\n>  * switch the rebase.backend default value from 'am' to 'merge'.\n> \n> Areas I'd like reviewers to focus, in priority order:\n> \n>  * Patch 15 (Are we ready to switch the default backend? Or should we leave\n>    this patch out for now? Junio suggested we may be ready or at least are\n>    close[1])\n>  * Patch 1 (Does my empty handling make sense? Do others agree I fixed \n>    --keep-empty, or do they view it as breaking it?)\n>  * Patch 8 (Do the updates to the documentation of behavioral differences\n>    make sense? Is it too long?) * Patch 11 (okay to change the\n>       git-completion shell prompt slightly, especially in light of patches\n>       15 & 16? We did a prompt change previously when we merged the merge\n>       backend with the interactive one, so I assume so, but just want to\n>       make sure people have a chance to chime in.)\n>    \n>    \n> \n> If it's too soon to switch from the 'am' to 'merge' backend, we can just\n> drop the last patch and I'll resubmit it later. I generated this series\n> mostly through switching the default first and then watching what broke, but\n> moved the patch to the end to make it easy to drop.\n> \n> Briefly, reasons for switching the default backend boil down to the fact\n> that the am-backend drops information and thus limits what it can do. This\n> manifests in different ways:\n> \n>  * lack of tree information that would allow us to warn users that new files\n>    in old directories might want to move along with the other files that\n>    were renamed with those directories[1]\n>  * incorrect application of patches in the presence of non-unique context\n>    lines[2], which could be avoided with access to the original files\n>    involved.\n>  * less information available to annotate conflict markers (since am creates\n>    fake ancestors and commits on top of them, and doesn't have access to the\n>    original commits)\n> \n> [1] https://lore.kernel.org/git/xmqqa78d2qmk.fsf@gitster-ct.c.googlers.com/\n> [2] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n> [3] \n> https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n> \n> Elijah Newren (15):\n>   rebase: extend the options for handling of empty commits\n>   t3406: simplify an already simple test\n>   rebase, sequencer: remove the broken GIT_QUIET handling\n>   rebase: make sure to pass along the quiet flag to the sequencer\n>   rebase: fix handling of restrict_revision\n>   t3432: make these tests work with either am or merge backends\n>   rebase: allow more types of rebases to fast-forward\n>   git-rebase.txt: add more details about behavioral differences of\n>     backends\n>   rebase: move incompatibility checks between backend options a bit\n>     earlier\n>   rebase: add an --am option\n>   contrib: change the prompt for am-based rebases\n>   rebase tests: mark tests specific to the am-backend with --am\n>   rebase tests: repeat some tests using the merge backend instead of am\n>   rebase: make the backend configurable via config setting\n>   rebase: change the default backend from \"am\" to \"merge\"\n> \n>  Documentation/config/rebase.txt         |   8 ++\n>  Documentation/git-rebase.txt            | 150 ++++++++++++++++----\n>  builtin/rebase.c                        | 181 +++++++++++++++++++-----\n>  contrib/completion/git-prompt.sh        |   2 +-\n>  rebase-interactive.c                    |   4 +-\n>  rebase-interactive.h                    |   2 +-\n>  sequencer.c                             |  80 ++++++++---\n>  sequencer.h                             |   6 +-\n>  t/t3400-rebase.sh                       |  36 ++++-\n>  t/t3401-rebase-and-am-rename.sh         |   4 +-\n>  t/t3404-rebase-interactive.sh           |   2 +-\n>  t/t3406-rebase-message.sh               |  19 ++-\n>  t/t3407-rebase-abort.sh                 |   6 +-\n>  t/t3420-rebase-autostash.sh             |   2 +-\n>  t/t3421-rebase-topology-linear.sh       |   4 +-\n>  t/t3424-rebase-empty.sh                 |  89 ++++++++++++\n>  t/t3425-rebase-topology-merges.sh       |   8 +-\n>  t/t3427-rebase-subtree.sh               |  16 ++-\n>  t/t3432-rebase-fast-forward.sh          |  59 ++++----\n>  t/t3433-rebase-options-compatibility.sh |  13 +-\n>  t/t5407-post-rewrite-hook.sh            |  12 +-\n>  t/t5520-pull.sh                         |  27 +++-\n>  t/t6047-diff3-conflict-markers.sh       |  13 +-\n>  t/t7512-status-help.sh                  |  12 +-\n>  t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-\n>  t/t9903-bash-prompt.sh                  |   6 +-\n>  26 files changed, 582 insertions(+), 182 deletions(-)\n>  create mode 100755 t/t3424-rebase-empty.sh\n> \n> \n> base-commit: 12029dc57db23baef008e77db1909367599210ee\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1\n> Pull-Request: https://github.com/git/git/pull/679\n> \n\nBTW, I have investigated on the performance regression when split-index\nis enabled, I’ll give my conclusions next week.\n\nCheers,\nAlban\n\n"},{"id":"388665","messageId":"CABPp-BFWf204jnBf=pgO+USULVmJXw5riwj4sOWNxK8gciD9mg@mail.gmail.com","threadId":"52496","inReplyTo":"0387d5b2-6b53-a428-291b-2489a9447bf3@gmail.com","subject":"Re: [PATCH 00/15] rebase: make the default backend configurable","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-12-20T18:55:39Z","receivedAt":"2019-12-20T18:55:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Alban,\n\nOn Fri, Dec 20, 2019 at 10:53 AM Alban Gruin <alban.gruin@gmail.com> wrote:\n>\n> Hi Elijah,\n>\n> Le 20/12/2019 à 18:09, Elijah Newren via GitGitGadget a écrit :\n> > This series does a lot of work around making the default rebase backend\n> > configurable:\n> >\n>\n> BTW, I have investigated on the performance regression when split-index\n> is enabled, I’ll give my conclusions next week.\n>\n> Cheers,\n> Alban\n\nNice!\n"},{"id":"388677","messageId":"xmqqimma8z3t.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"13e2056e780b00baf86d4020c0974b6b05ce115b.1576861788.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/15] rebase: extend the options for handling of empty commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-20T21:29:42Z","receivedAt":"2019-12-20T21:29:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Extend the interactive machinery with the ability to handle the full\n> spread of options for how to handle commits that either start or become\n> empty (by \"become empty\" I mean the changes in a commit are a subset of\n> changes that exist upstream, so the net effect of applying the commit is\n> no changes).  Introduce a new command line flag for selecting the\n> desired behavior:\n>     --empty={drop,keep,ask}\n> with the definitions:\n>     drop: drop empty commits\n>     keep: keep empty commits\n>     ask:  provide the user a chance to interact and pick what to do with\n>           empty commits on a case-by-case basis\n\nThis looks like a logical and natural extension of the --keep-empty\noption.\n\nAfter seeing the stress on \"empty from the beginning and ending up\nto be empty\" in the description, I somehow expected that we may be\nable to specify what happens to the empty commit separately, but\nthat does not seem to be what the patch is about, which was somewhat\ndisappointing.\n\n> +static long parse_empty_value(const char *value)\n> +{\n> +\tif (!value)\n> +\t\treturn EMPTY_UNSPECIFIED;\n> +\telse if (!strcasecmp(value, \"drop\"))\n> +\t\treturn EMPTY_DROP;\n> +\telse if (!strcasecmp(value, \"keep\"))\n> +\t\treturn EMPTY_KEEP;\n> +\telse if (!strcasecmp(value, \"ask\"))\n> +\t\treturn EMPTY_ASK;\n\nNot an error but just silently ignored?\n\n> +\treturn EMPTY_UNSPECIFIED;\n> +}\n> +\n\n> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tstruct rebase_options *options = opt->value;\n> +\tlong value = parse_empty_value(arg);\n\nAhh, OK.\n\nWouldn't it be better to make the variable and the parsing helper\nfunction of type \"enum empty_type\", not \"long\", just like the field\nin the rebase_options struct?\n\n> +\tBUG_ON_OPT_NEG(unset);\n> +\tif (value < 0)\n> +\t\treturn error(_(\"option empty accepts \\\"drop\\\", \"\n> +\t\t\t       \"\\\"keep\\\", and \\\"ask\\\"\"));\n> +\n> +\toptions->empty = value;\n> +\treturn 0;\n> +}\n"},{"id":"388678","messageId":"xmqqeewy8yv4.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"b99feebae111814be75dd99b5547539f8a764339.1576861788.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-20T21:34:55Z","receivedAt":"2019-12-20T21:35:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> The GIT_QUIET environment variable was used to signal the non-am\n> backends that the rebase should perform quietly.  The preserve-merges\n> backend does not make use of the quiet flag anywhere (other than to\n> write out its state whenever it writes state), and this mechanism was\n> broken in the conversion from shell to C.  Since this environment\n> variable was specifically designed for scripts and the only backend that\n> would still use it is no longer a script, just gut this code.\n\nNice to see a useless code go ;-)\n"},{"id":"388679","messageId":"xmqqa77m8yq1.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"b84faa86846cba6fb3f4bfa1d4f216fbce3867eb.1576861788.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/15] rebase: fix handling of restrict_revision","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-20T21:37:58Z","receivedAt":"2019-12-20T21:38:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> restrict_revision in the original shell script was an excluded revision\n> range.  It is also treated that way by the am-backend.  In the\n> conversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\n> implement the logic to initialize $revisions in C\", 2018-08-28)), the\n> interactive-backend accidentally treated it as a positive revision\n> rather than a negated one.\n>\n> This was missed as there were no tests in the testsuite that tested an\n> interactive rebase with fork-point behavior.\n\nThanks.\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/rebase.c  |  4 ++--\n>  t/t3400-rebase.sh | 20 +++++++++++++++++++-\n>  2 files changed, 21 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index cc8f3f008f..b320bb3a30 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n>  \n>  \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n>  \tif (opts->restrict_revision)\n> -\t\targv_array_push(&make_script_args,\n> -\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n> +\t\targv_array_pushf(&make_script_args, \"^%s\",\n> +\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n>  \n>  \tret = sequencer_make_script(the_repository, &todo_list.buf,\n>  \t\t\t\t    make_script_args.argc, make_script_args.argv,\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 79762b989a..71fd6396cd 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n>  \tgit rebase master\n>  '\n>  \n> -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n> +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n>  \tgit checkout -b default-base master &&\n>  \tgit checkout -b default topic &&\n>  \tgit config branch.default.remote . &&\n>  \tgit config branch.default.merge refs/heads/default-base &&\n> +\tgit rebase --merge &&\n> +\tgit rev-parse --verify default-base >expect &&\n> +\tgit rev-parse default~1 >actual &&\n> +\ttest_cmp expect actual &&\n> +\tgit checkout default-base &&\n> +\tgit reset --hard HEAD^ &&\n> +\tgit checkout default &&\n> +\tgit rebase --merge &&\n> +\tgit rev-parse --verify default-base >expect &&\n> +\tgit rev-parse default~1 >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n> +\tgit checkout -B default-base master &&\n> +\tgit checkout -B default topic &&\n> +\tgit config branch.default.remote . &&\n> +\tgit config branch.default.merge refs/heads/default-base &&\n>  \tgit rebase &&\n>  \tgit rev-parse --verify default-base >expect &&\n>  \tgit rev-parse default~1 >actual &&\n"},{"id":"388696","messageId":"20191220230710.GA32750@szeder.dev","threadId":"52496","inReplyTo":"1c3f8ba328982e633e1431eba3fabcb230821ddc.1576861788.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 11/15] contrib: change the prompt for am-based rebases","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-12-20T23:07:10Z","receivedAt":"2019-12-20T23:07:19Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"The subject prefix for this file is most of the time 'bash prompt' or\n'git-prompt'.\n\nOn Fri, Dec 20, 2019 at 05:09:44PM +0000, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> The prompt for am-based rebases was REBASE, while for interactive-based\n> rebases was REBASE-i.  A while ago, we switched merge-based rebases from\n> using REBASE-m to REBASE-i via re-implementing the merge backend based\n> on the interactive backend.  We will soon be changing the default rebase\n> backend to the interactive one, meaning the default prompt will be\n> REBASE-i rather than REBASE.  We have also noted in the documentation\n> that currently am-specific options will be implemented in the\n> interactive backend, and even the --am flag may eventually imply an\n> interactive-based rebase.  As such, change the prompt for an am-based\n> rebase from REBASE to REBASE-a.\n\nI had a bit of a hard time following which rebase variant is\nimplemented with which backend and when it was changed, and... etc.\n\nMakes me wonder: do we really need those \"-i\", \"-m\" or \"-a\" suffixes?\nWhat benefit do they bring?  Why don't we just say \"REBASE\" in the\nprompt?\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  contrib/completion/git-prompt.sh | 2 +-\n>  t/t9903-bash-prompt.sh           | 6 +++---\n>  2 files changed, 4 insertions(+), 4 deletions(-)\n> \n> diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\n> index 1d510cd47b..3c81099d60 100644\n> --- a/contrib/completion/git-prompt.sh\n> +++ b/contrib/completion/git-prompt.sh\n> @@ -440,7 +440,7 @@ __git_ps1 ()\n>  \t\t\t__git_eread \"$g/rebase-apply/last\" total\n>  \t\t\tif [ -f \"$g/rebase-apply/rebasing\" ]; then\n>  \t\t\t\t__git_eread \"$g/rebase-apply/head-name\" b\n> -\t\t\t\tr=\"|REBASE\"\n> +\t\t\t\tr=\"|REBASE-a\"\n>  \t\t\telif [ -f \"$g/rebase-apply/applying\" ]; then\n>  \t\t\t\tr=\"|AM\"\n>  \t\t\telse\n> diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\n> index 88bc733ad6..8da5b1aee2 100755\n> --- a/t/t9903-bash-prompt.sh\n> +++ b/t/t9903-bash-prompt.sh\n> @@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n>  \ttest_cmp expected \"$actual\"\n>  '\n>  \n> -test_expect_success 'prompt - rebase' '\n> -\tprintf \" (b2|REBASE 1/3)\" >expected &&\n> +test_expect_success 'prompt - rebase am' '\n> +\tprintf \" (b2|REBASE-a 1/3)\" >expected &&\n>  \tgit checkout b2 &&\n>  \ttest_when_finished \"git checkout master\" &&\n> -\ttest_must_fail git rebase b1 b2 &&\n> +\ttest_must_fail git rebase --am b1 b2 &&\n>  \ttest_when_finished \"git rebase --abort\" &&\n>  \t__git_ps1 >\"$actual\" &&\n>  \ttest_cmp expected \"$actual\"\n> -- \n> gitgitgadget\n> \n"},{"id":"388698","messageId":"CABPp-BGu503MgO6zudSnHoaeMksinYvT+YkWTP+VzmDweu7p7g@mail.gmail.com","threadId":"52496","inReplyTo":"20191220230710.GA32750@szeder.dev","subject":"Re: [PATCH 11/15] contrib: change the prompt for am-based rebases","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-12-21T00:17:02Z","receivedAt":"2019-12-21T00:17:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Dec 20, 2019 at 3:07 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:\n>\n> The subject prefix for this file is most of the time 'bash prompt' or\n> 'git-prompt'.\n>\n> On Fri, Dec 20, 2019 at 05:09:44PM +0000, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > The prompt for am-based rebases was REBASE, while for interactive-based\n> > rebases was REBASE-i.  A while ago, we switched merge-based rebases from\n> > using REBASE-m to REBASE-i via re-implementing the merge backend based\n> > on the interactive backend.  We will soon be changing the default rebase\n> > backend to the interactive one, meaning the default prompt will be\n> > REBASE-i rather than REBASE.  We have also noted in the documentation\n> > that currently am-specific options will be implemented in the\n> > interactive backend, and even the --am flag may eventually imply an\n> > interactive-based rebase.  As such, change the prompt for an am-based\n> > rebase from REBASE to REBASE-a.\n>\n> I had a bit of a hard time following which rebase variant is\n> implemented with which backend and when it was changed, and... etc.\n\nSorry about that.  I could fix it up, but...\n\n> Makes me wonder: do we really need those \"-i\", \"-m\" or \"-a\" suffixes?\n> What benefit do they bring?  Why don't we just say \"REBASE\" in the\n> prompt?\n\nOoh, I like this idea.  A lot easier to explain in the commit message\nand probably less confusing for users as well.\n"},{"id":"388699","messageId":"CABPp-BF5MaO60GAXbqYPfEePKyn_K-B_hQXNvTN_L4S6iA_93w@mail.gmail.com","threadId":"52496","inReplyTo":"xmqqimma8z3t.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-12-21T00:32:39Z","receivedAt":"2019-12-21T00:32:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Dec 20, 2019 at 1:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Extend the interactive machinery with the ability to handle the full\n> > spread of options for how to handle commits that either start or become\n> > empty (by \"become empty\" I mean the changes in a commit are a subset of\n> > changes that exist upstream, so the net effect of applying the commit is\n> > no changes).  Introduce a new command line flag for selecting the\n> > desired behavior:\n> >     --empty={drop,keep,ask}\n> > with the definitions:\n> >     drop: drop empty commits\n> >     keep: keep empty commits\n> >     ask:  provide the user a chance to interact and pick what to do with\n> >           empty commits on a case-by-case basis\n>\n> This looks like a logical and natural extension of the --keep-empty\n> option.\n>\n> After seeing the stress on \"empty from the beginning and ending up\n> to be empty\" in the description, I somehow expected that we may be\n> able to specify what happens to the empty commit separately, but\n> that does not seem to be what the patch is about, which was somewhat\n> disappointing.\n\nMy main reason for stressing both was that I've commits and code that\ntalked about \"empty\" commits, but which often only dealt with one of\nthe two types (sometimes when I would expect both to have been\naddressed), so I was just trying to be careful in my explanation of\nwhat it was about.\n\nIf wanted, we could implement finer control, but I don't know what the\nUI would look like or what the range of options we might want.  Do we\nwant to mix all possible permutations and have two flags, e.g.\n--become-empty=drop --start-empty=keep or --start-empty=ask\n--become-empty=keep?  Would --empty=<whatever> imply both\n--become-empty=<whatever> and --start-empty=<whatever> or would users\nneed to specify both?  Do we think all the permutations make sense, or\nwould we want to limit users to some subset?\n\nThe current choice of three covered the things that I had wanted in\nthe past, and which I think made for good defaults for various\ndifferent flags, so I just implemented it.  We could always extend it\nlater.\n\n>\n> > +static long parse_empty_value(const char *value)\n> > +{\n> > +     if (!value)\n> > +             return EMPTY_UNSPECIFIED;\n> > +     else if (!strcasecmp(value, \"drop\"))\n> > +             return EMPTY_DROP;\n> > +     else if (!strcasecmp(value, \"keep\"))\n> > +             return EMPTY_KEEP;\n> > +     else if (!strcasecmp(value, \"ask\"))\n> > +             return EMPTY_ASK;\n>\n> Not an error but just silently ignored?\n\nOops, I'll switch it to an error.\n\n> > +     return EMPTY_UNSPECIFIED;\n> > +}\n> > +\n>\n> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     struct rebase_options *options = opt->value;\n> > +     long value = parse_empty_value(arg);\n>\n> Ahh, OK.\n>\n> Wouldn't it be better to make the variable and the parsing helper\n> function of type \"enum empty_type\", not \"long\", just like the field\n> in the rebase_options struct?\n\nIndeed, I'll fix this up.\n"},{"id":"388714","messageId":"CABPp-BHZ4nYaAN237w70cen=P0M3-TsWQz_+BeU_VtYEbRCbAQ@mail.gmail.com","threadId":"52496","inReplyTo":"CABPp-BF5MaO60GAXbqYPfEePKyn_K-B_hQXNvTN_L4S6iA_93w@mail.gmail.com","subject":"Re: [PATCH 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-12-21T18:52:01Z","receivedAt":"2019-12-21T18:52:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Dec 20, 2019 at 4:32 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Fri, Dec 20, 2019 at 1:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n\n> > > +static long parse_empty_value(const char *value)\n> > > +{\n> > > +     if (!value)\n> > > +             return EMPTY_UNSPECIFIED;\n> > > +     else if (!strcasecmp(value, \"drop\"))\n> > > +             return EMPTY_DROP;\n> > > +     else if (!strcasecmp(value, \"keep\"))\n> > > +             return EMPTY_KEEP;\n> > > +     else if (!strcasecmp(value, \"ask\"))\n> > > +             return EMPTY_ASK;\n> >\n> > Not an error but just silently ignored?\n>\n> Oops, I'll switch it to an error.\n\nBy the way, diff.c's parse_algorithm_value() and\ndiff_opt_diff_algorithm() behave the same way, where\nparse_algorithm_value() will return a negative value if it's not one\nof the valid known cases, and diff_opt_diff_algorithm() checks for\nnegative and throws an error.  Should I also modify those to put the\nerror in parse_algorithm_value()?  (Not part of this series, but as a\nconsistency cleanup since it clearly surprised you and even I forgot\nthat I had the calling function returning the error when I copied that\nstyle?)\n"},{"id":"388768","messageId":"xmqqimm96xzc.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BF5MaO60GAXbqYPfEePKyn_K-B_hQXNvTN_L4S6iA_93w@mail.gmail.com","subject":"Re: [PATCH 01/15] rebase: extend the options for handling of empty commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-21T23:49:11Z","receivedAt":"2019-12-21T23:49:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> > +static long parse_empty_value(const char *value)\n>> > +{\n>> > +     if (!value)\n>> > +             return EMPTY_UNSPECIFIED;\n>> > +     else if (!strcasecmp(value, \"drop\"))\n>> > +             return EMPTY_DROP;\n>> > +     else if (!strcasecmp(value, \"keep\"))\n>> > +             return EMPTY_KEEP;\n>> > +     else if (!strcasecmp(value, \"ask\"))\n>> > +             return EMPTY_ASK;\n>>\n>> Not an error but just silently ignored?\n>\n> Oops, I'll switch it to an error.\n\nNot necessarily (see \"Ahh, OK\" below).\n\n>> > +     return EMPTY_UNSPECIFIED;\n>> > +}\n>> > +\n>>\n>> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n>> > +{\n>> > +     struct rebase_options *options = opt->value;\n>> > +     long value = parse_empty_value(arg);\n>>\n>> Ahh, OK.\n>>\n>> Wouldn't it be better to make the variable and the parsing helper\n>> function of type \"enum empty_type\", not \"long\", just like the field\n>> in the rebase_options struct?\n>\n> Indeed, I'll fix this up.\n\nThanks. I was wondering if there is something subtle I didn't see\ngoing on around here.\n"},{"id":"388777","messageId":"20191222051106.GA28890@generichostname","threadId":"52496","inReplyTo":"9b1ad46c58b7f8397acbf2a8339e150dfb04c956.1576861788.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-12-22T05:11:06Z","receivedAt":"2019-12-22T05:11:31Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Elijah,\n\nOn Fri, Dec 20, 2019 at 05:09:39PM +0000, Elijah Newren via GitGitGadget wrote:\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 92f95b57da..3879a43fa0 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -44,19 +44,16 @@ test_rebase_same_head_ () {\n>  \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n>  \t\toldhead=\\$(git rev-parse HEAD) &&\n>  \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n> +\t\tcp .git/logs/HEAD expect &&\n>  \t\tgit rebase$flag $* >stdout &&\n>  \t\tif test $what = work\n>  \t\tthen\n> -\t\t\t# Must check this case first, for 'is up to\n> -\t\t\t# date, rebase forced[...]rewinding head' cases\n> -\t\t\ttest_i18ngrep 'rewinding head' stdout\n> +\t\t\twc -l .git/logs/HEAD >old &&\n> +\t\t\twc -l .git/logs/HEAD >new &&\n\nAre the above two lines some yet-to-be cleaned up leftovers? I can't see\nwhere `old` and `new` are being used elsewhere.\n\n> +\t\t\ttest_line_count '-gt' $(($old + 2)) .git/logs/HEAD\n>  \t\telif test $what = noop\n>  \t\tthen\n> -\t\t\ttest_i18ngrep 'is up to date' stdout &&\n> -\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n> -\t\telif test $what = noop-force\n> -\t\tthen\n> -\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n> +\t\t\ttest_cmp expect .git/logs/HEAD\n>  \t\tfi &&\n>  \t\tnewhead=\\$(git rev-parse HEAD) &&\n>  \t\tif test $cmp = same\n"},{"id":"388810","messageId":"CABPp-BHXZHaQk--6=8-BBLqmCeRrLXYXRRru_gbVKUUdGAWqXQ@mail.gmail.com","threadId":"52496","inReplyTo":"20191222051106.GA28890@generichostname","subject":"Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-12-23T17:17:22Z","receivedAt":"2019-12-23T17:17:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sat, Dec 21, 2019 at 9:11 PM Denton Liu <liu.denton@gmail.com> wrote:\n>\n> Hi Elijah,\n>\n> On Fri, Dec 20, 2019 at 05:09:39PM +0000, Elijah Newren via GitGitGadget wrote:\n> > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> > index 92f95b57da..3879a43fa0 100755\n> > --- a/t/t3432-rebase-fast-forward.sh\n> > +++ b/t/t3432-rebase-fast-forward.sh\n> > @@ -44,19 +44,16 @@ test_rebase_same_head_ () {\n> >       test_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n> >               oldhead=\\$(git rev-parse HEAD) &&\n> >               test_when_finished 'git reset --hard \\$oldhead' &&\n> > +             cp .git/logs/HEAD expect &&\n> >               git rebase$flag $* >stdout &&\n> >               if test $what = work\n> >               then\n> > -                     # Must check this case first, for 'is up to\n> > -                     # date, rebase forced[...]rewinding head' cases\n> > -                     test_i18ngrep 'rewinding head' stdout\n> > +                     wc -l .git/logs/HEAD >old &&\n> > +                     wc -l .git/logs/HEAD >new &&\n>\n> Are the above two lines some yet-to-be cleaned up leftovers? I can't see\n> where `old` and `new` are being used elsewhere.\n\nUm, this whole section is broken and causes it to pass the test for\nstupid reasons.  Not sure how I bungled that so badly.  I'll send a\nre-roll with a corrected 6/15.\n\n>\n> > +                     test_line_count '-gt' $(($old + 2)) .git/logs/HEAD\n> >               elif test $what = noop\n> >               then\n> > -                     test_i18ngrep 'is up to date' stdout &&\n> > -                     test_i18ngrep ! 'rebase forced' stdout\n> > -             elif test $what = noop-force\n> > -             then\n> > -                     test_i18ngrep 'is up to date, rebase forced' stdout\n> > +                     test_cmp expect .git/logs/HEAD\n> >               fi &&\n> >               newhead=\\$(git rev-parse HEAD) &&\n> >               if test $cmp = same\n"},{"id":"388813","messageId":"bd3c5ec155325d78f89afcbe910810cc18c0aaeb.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 02/15] t3406: simplify an already simple test","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:46Z","receivedAt":"2019-12-23T18:50:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen the merge backend was re-implemented on top of the interactive\nbackend, the output of rebase --merge changed a little.  This change\nallowed this test to be simplified, though it wasn't noticed until now.\nSimplify the testcase a little.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3406-rebase-message.sh | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex b393e1e9fe..0c2c569f95 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -18,11 +18,8 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'rebase -m' '\n-\tgit rebase -m master >report &&\n-\t>expect &&\n-\tsed -n -e \"/^Already applied: /p\" \\\n-\t\t-e \"/^Committed: /p\" report >actual &&\n-\ttest_cmp expect actual\n+\tgit rebase -m master >actual &&\n+\ttest_must_be_empty actual\n '\n \n test_expect_success 'rebase against master twice' '\n-- \ngitgitgadget\n\n"},{"id":"388814","messageId":"1c2b77e94d63f86590ca934855066eca278f576e.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:45Z","receivedAt":"2019-12-23T18:50:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nExtend the interactive machinery with the ability to handle the full\nspread of options for how to handle commits that either start or become\nempty (by \"become empty\" I mean the changes in a commit are a subset of\nchanges that exist upstream, so the net effect of applying the commit is\nno changes).  Introduce a new command line flag for selecting the\ndesired behavior:\n    --empty={drop,keep,ask}\nwith the definitions:\n    drop: drop empty commits\n    keep: keep empty commits\n    ask:  provide the user a chance to interact and pick what to do with\n          empty commits on a case-by-case basis\n\nNote that traditionally, am-based rebases have always dropped commits\nthat either started or became empty, while interactive-based rebases\nhave defaulted to ask (and provided an option to keep commits that\nstarted empty).  This difference made sense since users of an am-based\nrebase just wanted to quickly batch apply a sequence of commits, while\nusers editing a todo list will likely want the chance to interact and\nhandle unusual cases on a case-by-case basis.  However, not all rebases\nusing the interactive machinery are explicitly interactive anymore.  In\nparticular --merge was always meant to behave more like --am: just\nrebase a batch of commits without popping up a todo list.\n\nIf the --empty flag is not specified, pick defaults as follows:\n    explicitly interactive: ask\n    --exec: keep (exec is about checking existing commits, and often\n                  used without actually changing the base.  Thus the\n                  expectation is that the user doesn't necessarily want\n                  anything to change; they just want to test).\n    otherwise: drop\n\nAlso, this commit makes --keep-empty just imply --empty=keep, and hides\nit from help so that we aren't confusing users with different ways to do\nthe same thing.  (I could have added a --drop-empty flag, but then that\ninvites users to specify both --keep-empty and --drop-empty and we have\nto add sanity checking around that; it seems cleaner to have a single\nmulti-valued option.)  This actually fixes --keep-empty too; previously,\nit only meant to sometimes keep empty commits, in particular commits\nwhich started empty would be kept.  But it would still error out and ask\nthe user what to do with commits that became empty.  Now it keeps empty\ncommits, as instructed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt      | 35 ++++++------\n builtin/rebase.c                  | 83 +++++++++++++++++++++++++---\n rebase-interactive.c              |  4 +-\n rebase-interactive.h              |  2 +-\n sequencer.c                       | 74 +++++++++++++++++++------\n sequencer.h                       |  6 ++-\n t/t3421-rebase-topology-linear.sh |  4 +-\n t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++\n t/t3427-rebase-subtree.sh         | 16 +++---\n 9 files changed, 263 insertions(+), 50 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d0e2d27cc..ff32ca1080 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--empty={drop,keep,ask}::\n+\tHow to handle commits that become empty (because they contain a\n+\tsubset of already upstream changes) or start empty.  With drop\n+\t(the default), commits that start or become empty are dropped.\n+\tWith keep (implied by --exec), such commits are kept.  With ask\n+\t(implied by --interactive), the rebase will halt when an empty\n+\tcommit is applied allowing you to choose whether to drop it or\n+\tcommit it.  Also with ask, if the rebase is interactive then\n+\tcommits which start empty will be commented out in the todo\n+\taction list (giving you a chance to uncomment).\n++\n+Note that this has no effect on commits which are already upstream (as\n+can be checked via `git log --cherry-mark ...`), which are always\n+dropped by rebase.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --keep-empty::\n-\tKeep the commits that do not change anything from its\n-\tparents in the result.\n+\tDeprecated alias for what is now known as --empty=keep.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -569,6 +585,7 @@ are incompatible with the following options:\n  * --interactive\n  * --exec\n  * --keep-empty\n+ * --empty=\n  * --edit-todo\n  * --root when used in combination with --onto\n \n@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --ignore-whitespace\n  * --preserve-merges and --committer-date-is-author-date\n  * --preserve-merges and --ignore-date\n+ * --preserve-merges and --empty=\n  * --keep-base and --onto\n  * --keep-base and --root\n \n@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES\n \n There are some subtle differences how the backends behave.\n \n-Empty commits\n-~~~~~~~~~~~~~\n-\n-The am backend drops any \"empty\" commits, regardless of whether the\n-commit started empty (had no changes relative to its parent to\n-start with) or ended empty (all changes were already applied\n-upstream in other commits).\n-\n-The interactive backend drops commits by default that\n-started empty and halts if it hits a commit that ended up empty.\n-The `--keep-empty` option exists for the interactive backend to allow\n-it to keep commits that started empty.\n-\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ddf33bc9d4..6903249307 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -50,8 +50,16 @@ enum rebase_type {\n \tREBASE_PRESERVE_MERGES\n };\n \n+enum empty_type {\n+\tEMPTY_UNSPECIFIED = -1,\n+\tEMPTY_DROP,\n+\tEMPTY_KEEP,\n+\tEMPTY_ASK\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n+\tenum empty_type empty;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -77,7 +85,6 @@ struct rebase_options {\n \tconst char *action;\n \tint signoff;\n \tint allow_rerere_autoupdate;\n-\tint keep_empty;\n \tint autosquash;\n \tint ignore_whitespace;\n \tchar *gpg_sign_opt;\n@@ -95,6 +102,7 @@ struct rebase_options {\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n \treplay.allow_empty = 1;\n \treplay.allow_empty_message = opts->allow_empty_message;\n+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n+\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n+\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \n \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n \n-\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_ASK &&\n+\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n+\t\t\tTODO_LIST_ASK_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \treturn ret;\n }\n \n+static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->empty = EMPTY_KEEP;\n+\topts->type = REBASE_INTERACTIVE;\n+\treturn 0;\n+}\n+\n static const char * const builtin_rebase_interactive_usage[] = {\n \tN_(\"git rebase--interactive [<options>]\"),\n \tNULL\n@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n-\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\t N_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\topts->allow_rerere_autoupdate ?\n \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n-\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n+\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n@@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static enum empty_type parse_empty_value(const char *value)\n+{\n+\tif (!strcasecmp(value, \"drop\"))\n+\t\treturn EMPTY_DROP;\n+\telse if (!strcasecmp(value, \"keep\"))\n+\t\treturn EMPTY_KEEP;\n+\telse if (!strcasecmp(value, \"ask\"))\n+\t\treturn EMPTY_ASK;\n+\n+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n+}\n+\n+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\tenum empty_type value = parse_empty_value(arg);\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\toptions->empty = value;\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@@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n-\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n-\t\t\t N_(\"preserve empty commits during rebase\")),\n+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n+\t\t\t       N_(\"how to handle empty commits\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n@@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n-\tif (options.keep_empty)\n-\t\timply_interactive(&options, \"--keep-empty\");\n+\tif (options.empty != EMPTY_UNSPECIFIED)\n+\t\timply_interactive(&options, \"--empty\");\n \n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n@@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \t}\n \n+\tif (options.empty == EMPTY_UNSPECIFIED) {\n+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n+\t\t\toptions.empty = EMPTY_ASK;\n+\t\telse if (exec.nr > 0)\n+\t\t\toptions.empty = EMPTY_KEEP;\n+\t\telse\n+\t\t\toptions.empty = EMPTY_DROP;\n+\t}\n \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad82bf77df 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n \treturn MISSING_COMMIT_CHECK_IGNORE;\n }\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf)\n {\n@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,\n \n \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \n-\tif (!keep_empty) {\n+\tif (!no_ask_empty) {\n \t\tmsg = _(\"Note that empty commits are commented out\");\n \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \t}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..f531e00ba7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -5,7 +5,7 @@ struct strbuf;\n struct repository;\n struct todo_list;\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf);\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/sequencer.c b/sequencer.c\nindex 763ccbbc45..d2c11f34b7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n \n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,\n \tempty_commit = is_original_commit_empty(commit);\n \tif (empty_commit < 0)\n \t\treturn empty_commit;\n-\tif (!empty_commit)\n+\tif (!empty_commit || opts->ask_on_initially_empty)\n \t\treturn 0;\n \telse\n \t\treturn 1;\n@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,\n \tchar *author = NULL;\n \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n \tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res, unborn = 0, reword = 0, allow;\n+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n \n \tif (opts->no_commit) {\n \t\t/*\n@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,\n \t\tgoto leave;\n \t}\n \n-\tallow = allow_empty(r, opts, commit);\n-\tif (allow < 0) {\n-\t\tres = allow;\n-\t\tgoto leave;\n-\t} else if (allow)\n-\t\tflags |= ALLOW_EMPTY;\n-\tif (!opts->no_commit) {\n+\tdrop_commit = 0;\n+\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n+\t\tdrop_commit = 1;\n+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n+\t} else {\n+\t\tallow = allow_empty(r, opts, commit);\n+\t\tif (allow < 0) {\n+\t\t\tres = allow;\n+\t\t\tgoto leave;\n+\t\t} else if (allow) {\n+\t\t\tflags |= ALLOW_EMPTY;\n+\t\t}\n+\t}\n+\tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n \t\telse\n@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n+\t\topts->drop_redundant_commits =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n+\t\topts->ask_on_initially_empty =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n \t\t\topts->reschedule_failed_exec = 1;\n \n+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n+\t\t\topts->drop_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n+\t\t\topts->keep_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n+\t\t\topts->ask_on_initially_empty = 1;\n+\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n \tif (opts->ignore_date)\n \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n+\tif (opts->drop_redundant_commits)\n+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n+\tif (opts->keep_redundant_commits)\n+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n+\tif (opts->ask_on_initially_empty)\n+\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n \tif (opts->reschedule_failed_exec)\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \n@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n \tif (opts->allow_empty_message)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.allow-empty-message\", \"true\");\n+\tif (opts->drop_redundant_commits)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n \tif (opts->keep_redundant_commits)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n+\tif (opts->ask_on_initially_empty)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n \tif (opts->signoff)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\t\"options.signoff\", \"true\");\n@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n \t\t\t\t   unsigned flags)\n {\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tis_empty = is_original_commit_empty(commit);\n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n \n \t\tstrbuf_reset(&oneline);\n \t\tpretty_print_commit(pp, commit, &oneline);\n@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tif (!to_merge) {\n \t\t\t/* non-merge commit: easy case */\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tif (!keep_empty && is_empty)\n+\t\t\tif (is_empty && ask_empty)\n \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n \t\t\t\t    oid_to_hex(&commit->object.oid),\n@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \tstruct pretty_print_context pp = {0};\n \tstruct rev_info revs;\n \tstruct commit *commit;\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n \n \twhile ((commit = get_revision(&revs))) {\n-\t\tint is_empty  = is_original_commit_empty(commit);\n+\t\tint is_empty = is_original_commit_empty(commit);\n \n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n-\t\tif (!keep_empty && is_empty)\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n+\t\tif (is_empty && ask_empty)\n \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n \t\tstrbuf_addf(out, \"%s %s \", insn,\n \t\t\t    oid_to_hex(&commit->object.oid));\n@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \n \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n-\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n+\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n+\t\t\t\t count_commands(todo_list),\n \t\t\t\t shortrevisions, shortonto, &buf);\n \n \tres = write_message(buf.buf, buf.len, file, 0);\ndiff --git a/sequencer.h b/sequencer.h\nindex e9a0e03ea2..1c3abb661c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -39,7 +39,9 @@ struct replay_opts {\n \tint allow_rerere_auto;\n \tint allow_empty;\n \tint allow_empty_message;\n+\tint drop_redundant_commits;\n \tint keep_redundant_commits;\n+\tint ask_on_initially_empty;\n \tint verbose;\n \tint quiet;\n \tint reschedule_failed_exec;\n@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-#define TODO_LIST_KEEP_EMPTY (1U << 0)\n+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);\n  * `--onto`, we do not want to re-generate the root commits.\n  */\n #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n+#define TODO_LIST_DROP_EMPTY (1U << 7)\n+#define TODO_LIST_ASK_EMPTY (1U << 8)\n \n \n int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 325072b0a3..d23e0bf778 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -230,7 +230,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -245,7 +245,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n test_run_rebase success --rebase-merges\n \n #       m\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nnew file mode 100755\nindex 0000000000..9d52e1417f\n--- /dev/null\n+++ b/t/t3424-rebase-empty.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='git rebase of commits that start or become empty'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup test repository' '\n+\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n+\ttest_write_lines A B C D E F G H I J >letters &&\n+\tgit add numbers letters &&\n+\tgit commit -m A &&\n+\n+\tgit branch upstream &&\n+\tgit branch localmods &&\n+\n+\tgit checkout upstream &&\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m B &&\n+\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C &&\n+\n+\tgit checkout localmods &&\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C2 &&\n+\n+\tgit commit --allow-empty -m D &&\n+\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m \"Five letters ought to be enough for anybody\"\n+'\n+\n+test_expect_success 'rebase --merge --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n+\n+\ttest_must_fail git rebase --skip &&\n+\tgit commit --allow-empty &&\n+\tgit rebase --continue &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n+\n+test_expect_success 'rebase --interactive --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_done\ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex bec48e6a1f..468ebc1bef 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n-- \ngitgitgadget\n\n"},{"id":"388815","messageId":"478479358ff21afe6b8374f076d2ed5e139ae6c0.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 04/15] rebase: make sure to pass along the quiet flag to the sequencer","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:48Z","receivedAt":"2019-12-23T18:50:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  | 3 ++-\n t/t3400-rebase.sh | 8 +++++++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 32026a62e8..5014c9a437 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n \treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n \t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n+\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -1502,7 +1503,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n \t\t\t   N_(\"be quiet. implies --no-stat\"),\n-\t\t\t   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),\n+\t\t\t   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\n \t\tOPT_BIT('v', \"verbose\", &options.flags,\n \t\t\tN_(\"display a diffstat of what changed upstream\"),\n \t\t\tREBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 221b35f2df..79762b989a 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase -q is quiet' '\n+test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n \tgit rebase -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n+test_expect_success 'rebase --merge -q is quiet' '\n+\tgit checkout -B quiet topic &&\n+\tgit rebase --merge -q master >output.out 2>&1 &&\n+\ttest_must_be_empty output.out\n+'\n+\n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n \t\techo \"One\" &&\n-- \ngitgitgadget\n\n"},{"id":"388816","messageId":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.git.git.1576861788.gitgitgadget@gmail.com","subject":"[PATCH v2 00/15] rebase: make the default backend configurable","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:44Z","receivedAt":"2019-12-23T18:50:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series does a lot of work around making the default rebase backend\nconfigurable, and switching the default from the am backend to the\nmerge/interactive one.\n\nSee the cover letter for v1[*] for a more detailed motivation for the\nseries, the type of changes being made, and areas I'd like to reviewers to\nfocus on.\n\nChanges since v1:\n\n * Made some cleanups to the empty type parsing, as suggested by Junio\n   (Patch 1)\n * Changed the shell prompt for all rebases to just be \"REBASE\", as\n   suggested by SZEDER (Patch 11)\n * Correctly check that work was done (by checking that reflog grew, patch\n   6); flagged by Denton\n\n[*] \nhttps://lore.kernel.org/git/pull.679.git.git.1576861788.gitgitgadget@gmail.com/\n\nElijah Newren (15):\n  rebase: extend the options for handling of empty commits\n  t3406: simplify an already simple test\n  rebase, sequencer: remove the broken GIT_QUIET handling\n  rebase: make sure to pass along the quiet flag to the sequencer\n  rebase: fix handling of restrict_revision\n  t3432: make these tests work with either am or merge backends\n  rebase: allow more types of rebases to fast-forward\n  git-rebase.txt: add more details about behavioral differences of\n    backends\n  rebase: move incompatibility checks between backend options a bit\n    earlier\n  rebase: add an --am option\n  contrib: change the prompt for interactive-based rebases\n  rebase tests: mark tests specific to the am-backend with --am\n  rebase tests: repeat some tests using the merge backend instead of am\n  rebase: make the backend configurable via config setting\n  rebase: change the default backend from \"am\" to \"merge\"\n\n Documentation/config/rebase.txt         |   8 ++\n Documentation/git-rebase.txt            | 150 ++++++++++++++++----\n builtin/rebase.c                        | 177 +++++++++++++++++++-----\n contrib/completion/git-prompt.sh        |   4 +-\n rebase-interactive.c                    |   4 +-\n rebase-interactive.h                    |   2 +-\n sequencer.c                             |  80 ++++++++---\n sequencer.h                             |   6 +-\n t/t3400-rebase.sh                       |  36 ++++-\n t/t3401-rebase-and-am-rename.sh         |   4 +-\n t/t3404-rebase-interactive.sh           |   2 +-\n t/t3406-rebase-message.sh               |  19 ++-\n t/t3407-rebase-abort.sh                 |   6 +-\n t/t3420-rebase-autostash.sh             |   2 +-\n t/t3421-rebase-topology-linear.sh       |   4 +-\n t/t3424-rebase-empty.sh                 |  89 ++++++++++++\n t/t3425-rebase-topology-merges.sh       |   8 +-\n t/t3427-rebase-subtree.sh               |  16 ++-\n t/t3432-rebase-fast-forward.sh          |  54 ++++----\n t/t3433-rebase-options-compatibility.sh |  13 +-\n t/t5407-post-rewrite-hook.sh            |  12 +-\n t/t5520-pull.sh                         |  27 +++-\n t/t6047-diff3-conflict-markers.sh       |  13 +-\n t/t7512-status-help.sh                  |  12 +-\n t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-\n t/t9903-bash-prompt.sh                  |   8 +-\n 26 files changed, 577 insertions(+), 182 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\n\nbase-commit: 12029dc57db23baef008e77db1909367599210ee\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v2\nPull-Request: https://github.com/git/git/pull/679\n\nRange-diff vs v1:\n\n  1:  13e2056e78 !  1:  1c2b77e94d rebase: extend the options for handling of empty commits\n     @@ -219,28 +219,24 @@\n       \treturn 0;\n       }\n       \n     -+static long parse_empty_value(const char *value)\n     ++static enum empty_type parse_empty_value(const char *value)\n      +{\n     -+\tif (!value)\n     -+\t\treturn EMPTY_UNSPECIFIED;\n     -+\telse if (!strcasecmp(value, \"drop\"))\n     ++\tif (!strcasecmp(value, \"drop\"))\n      +\t\treturn EMPTY_DROP;\n      +\telse if (!strcasecmp(value, \"keep\"))\n      +\t\treturn EMPTY_KEEP;\n      +\telse if (!strcasecmp(value, \"ask\"))\n      +\t\treturn EMPTY_ASK;\n     -+\treturn EMPTY_UNSPECIFIED;\n     ++\n     ++\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n      +}\n      +\n      +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n      +{\n      +\tstruct rebase_options *options = opt->value;\n     -+\tlong value = parse_empty_value(arg);\n     ++\tenum empty_type value = parse_empty_value(arg);\n      +\n      +\tBUG_ON_OPT_NEG(unset);\n     -+\tif (value < 0)\n     -+\t\treturn error(_(\"option empty accepts \\\"drop\\\", \"\n     -+\t\t\t       \"\\\"keep\\\", and \\\"ask\\\"\"));\n      +\n      +\toptions->empty = value;\n      +\treturn 0;\n  2:  47ea99fb30 =  2:  bd3c5ec155 t3406: simplify an already simple test\n  3:  b99feebae1 =  3:  49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling\n  4:  66bee10d1c =  4:  478479358f rebase: make sure to pass along the quiet flag to the sequencer\n  5:  b84faa8684 =  5:  ee26f5a161 rebase: fix handling of restrict_revision\n  6:  9b1ad46c58 !  6:  34a69def33 t3432: make these tests work with either am or merge backends\n     @@ -37,9 +37,8 @@\n      -\t\t\t# Must check this case first, for 'is up to\n      -\t\t\t# date, rebase forced[...]rewinding head' cases\n      -\t\t\ttest_i18ngrep 'rewinding head' stdout\n     -+\t\t\twc -l .git/logs/HEAD >old &&\n     -+\t\t\twc -l .git/logs/HEAD >new &&\n     -+\t\t\ttest_line_count '-gt' $(($old + 2)) .git/logs/HEAD\n     ++\t\t\told=\\$(wc -l <expect) &&\n     ++\t\t\ttest_line_count '-gt' \\$old .git/logs/HEAD\n       \t\telif test $what = noop\n       \t\tthen\n      -\t\t\ttest_i18ngrep 'is up to date' stdout &&\n     @@ -108,10 +107,3 @@\n       test_rebase_same_head success noop same success work diff --onto master... master\n       test_rebase_same_head success noop same success work diff --keep-base master\n       test_rebase_same_head success noop same success work diff --keep-base\n     --test_rebase_same_head failure work same success work diff --fork-point --onto B B\n     --test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n     -+test_rebase_same_head success work same success work diff --fork-point --onto B B\n     -+test_rebase_same_head success work same success work diff --fork-point --onto B... B\n     - test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n     - test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n     - \n  7:  a4073df910 =  7:  f2c92853b4 rebase: allow more types of rebases to fast-forward\n  8:  90e8927ea0 =  8:  b307340f7c git-rebase.txt: add more details about behavioral differences of backends\n  9:  5c31d664f3 =  9:  7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier\n 10:  340bf71c5c = 10:  1df11f0b51 rebase: add an --am option\n 11:  1c3f8ba328 ! 11:  94b5a3051d contrib: change the prompt for am-based rebases\n     @@ -1,17 +1,38 @@\n      Author: Elijah Newren <newren@gmail.com>\n      \n     -    contrib: change the prompt for am-based rebases\n     -\n     -    The prompt for am-based rebases was REBASE, while for interactive-based\n     -    rebases was REBASE-i.  A while ago, we switched merge-based rebases from\n     -    using REBASE-m to REBASE-i via re-implementing the merge backend based\n     -    on the interactive backend.  We will soon be changing the default rebase\n     -    backend to the interactive one, meaning the default prompt will be\n     -    REBASE-i rather than REBASE.  We have also noted in the documentation\n     -    that currently am-specific options will be implemented in the\n     -    interactive backend, and even the --am flag may eventually imply an\n     -    interactive-based rebase.  As such, change the prompt for an am-based\n     -    rebase from REBASE to REBASE-a.\n     +    contrib: change the prompt for interactive-based rebases\n     +\n     +    In the past, we had different prompts for different types of rebases:\n     +       REBASE: for am-based rebases\n     +       REBASE-m: for merge-based rebases\n     +       REBASE-i: for interactive-based rebases\n     +\n     +    It's not clear why this distinction was necessary or helpful; when the\n     +    prompt was added in commit e75201963f67 (\"Improve bash prompt to detect\n     +    various states like an unfinished merge\", 2007-09-30), it simply added\n     +    these three different types.  Perhaps there was a useful purpose back\n     +    then, but there have been some changes:\n     +\n     +      * The merge backend was deleted after being implemented on top of the\n     +        interactive backend, causing the prompt for merge-based rebases to\n     +        change from REBASE-m to REBASE-i.\n     +      * The interactive backend is used for multiple different types of\n     +        non-interactive rebases, so the \"-i\" part of the prompt doesn't\n     +        really mean what it used to.\n     +      * Rebase backends have gained more abilities and have a great deal of\n     +        overlap, sometimes making it hard to distinguish them.\n     +      * Behavioral differences between the backends have also been ironed\n     +        out.\n     +      * We want to change the default backend from am to interactive, which\n     +        means people would get \"REBASE-i\" by default if we didn't change\n     +        the prompt, and only if they specified --am or --whitespace or -C\n     +        would they get the \"REBASE\" prompt.\n     +      * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n     +        run the interactive backend once it can handle everything the\n     +        am-backend can.\n     +\n     +    For all these reasons, make the prompt for any type of rebase just be\n     +    \"REBASE\".\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ -19,26 +40,46 @@\n       --- a/contrib/completion/git-prompt.sh\n       +++ b/contrib/completion/git-prompt.sh\n      @@\n     - \t\t\t__git_eread \"$g/rebase-apply/last\" total\n     - \t\t\tif [ -f \"$g/rebase-apply/rebasing\" ]; then\n     - \t\t\t\t__git_eread \"$g/rebase-apply/head-name\" b\n     --\t\t\t\tr=\"|REBASE\"\n     -+\t\t\t\tr=\"|REBASE-a\"\n     - \t\t\telif [ -f \"$g/rebase-apply/applying\" ]; then\n     - \t\t\t\tr=\"|AM\"\n     - \t\t\telse\n     + \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n     + \t\t__git_eread \"$g/rebase-merge/end\" total\n     + \t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n     +-\t\t\tr=\"|REBASE-i\"\n     ++\t\t\tr=\"|REBASE\"\n     + \t\telse\n     +-\t\t\tr=\"|REBASE-m\"\n     ++\t\t\tr=\"|REBASE\"\n     + \t\tfi\n     + \telse\n     + \t\tif [ -d \"$g/rebase-apply\" ]; then\n      \n       diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\n       --- a/t/t9903-bash-prompt.sh\n       +++ b/t/t9903-bash-prompt.sh\n     +@@\n     + '\n     + \n     + test_expect_success 'prompt - interactive rebase' '\n     +-\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n     ++\tprintf \" (b1|REBASE 2/3)\" >expected &&\n     + \twrite_script fake_editor.sh <<-\\EOF &&\n     + \t\techo \"exec echo\" >\"$1\"\n     + \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n     +@@\n     + '\n     + \n     + test_expect_success 'prompt - rebase merge' '\n     +-\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n     ++\tprintf \" (b2|REBASE 1/3)\" >expected &&\n     + \tgit checkout b2 &&\n     + \ttest_when_finished \"git checkout master\" &&\n     + \ttest_must_fail git rebase --merge b1 b2 &&\n      @@\n       \ttest_cmp expected \"$actual\"\n       '\n       \n      -test_expect_success 'prompt - rebase' '\n     --\tprintf \" (b2|REBASE 1/3)\" >expected &&\n      +test_expect_success 'prompt - rebase am' '\n     -+\tprintf \" (b2|REBASE-a 1/3)\" >expected &&\n     + \tprintf \" (b2|REBASE 1/3)\" >expected &&\n       \tgit checkout b2 &&\n       \ttest_when_finished \"git checkout master\" &&\n      -\ttest_must_fail git rebase b1 b2 &&\n 12:  bcac3b77e8 = 12:  c905d288bf rebase tests: mark tests specific to the am-backend with --am\n 13:  81d0f18b3e = 13:  0287881361 rebase tests: repeat some tests using the merge backend instead of am\n 14:  04db4472d3 = 14:  ec782e711c rebase: make the backend configurable via config setting\n 15:  999587933b = 15:  7adcbc0bc5 rebase: change the default backend from \"am\" to \"merge\"\n\n-- \ngitgitgadget\n"},{"id":"388819","messageId":"49388b79fddc339f3a5df747406621ec5ad28789.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 03/15] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:47Z","receivedAt":"2019-12-23T18:50:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe GIT_QUIET environment variable was used to signal the non-am\nbackends that the rebase should perform quietly.  The preserve-merges\nbackend does not make use of the quiet flag anywhere (other than to\nwrite out its state whenever it writes state), and this mechanism was\nbroken in the conversion from shell to C.  Since this environment\nvariable was specifically designed for scripts and the only backend that\nwould still use it is no longer a script, just gut this code.\n\nA subsequent commit will fix --quiet for the interactive/merge backend\nin a different way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 6 ++----\n sequencer.c      | 6 ++----\n 2 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 6903249307..32026a62e8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t   opts->onto ? oid_to_hex(&opts->onto->object.oid) : \"\");\n \twrite_file(state_dir_path(\"orig-head\", opts), \"%s\",\n \t\t   oid_to_hex(&opts->orig_head));\n-\twrite_file(state_dir_path(\"quiet\", opts), \"%s\",\n-\t\t   opts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n+\tif (!(opts->flags & REBASE_NO_QUIET))\n+\t\twrite_file(state_dir_path(\"quiet\", opts), \"%s\", \"\");\n \tif (opts->flags & REBASE_VERBOSE)\n \t\twrite_file(state_dir_path(\"verbose\", opts), \"%s\", \"\");\n \tif (opts->strategy)\n@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"revisions\", opts->revisions);\n \tadd_var(&script_snippet, \"restrict_revision\", opts->restrict_revision ?\n \t\toid_to_hex(&opts->restrict_revision->object.oid) : NULL);\n-\tadd_var(&script_snippet, \"GIT_QUIET\",\n-\t\topts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n \tsq_quote_argv_pretty(&buf, opts->git_am_opts.argv);\n \tadd_var(&script_snippet, \"git_am_opt\", buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/sequencer.c b/sequencer.c\nindex d2c11f34b7..71062212a5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)\n int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\t      struct commit *onto, const char *orig_head)\n {\n-\tconst char *quiet = getenv(\"GIT_QUIET\");\n-\n \tif (head_name)\n \t\twrite_file(rebase_path_head_name(), \"%s\\n\", head_name);\n \tif (onto)\n@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \tif (orig_head)\n \t\twrite_file(rebase_path_orig_head(), \"%s\\n\", orig_head);\n \n-\tif (quiet)\n-\t\twrite_file(rebase_path_quiet(), \"%s\\n\", quiet);\n+\tif (opts->quiet)\n+\t\twrite_file(rebase_path_quiet(), \"%s\", \"\");\n \tif (opts->verbose)\n \t\twrite_file(rebase_path_verbose(), \"%s\", \"\");\n \tif (opts->strategy)\n-- \ngitgitgadget\n\n"},{"id":"388817","messageId":"b307340f7c79c87b2b5a5a2a25d98ef68be4ba4f.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 08/15] git-rebase.txt: add more details about behavioral differences of backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:52Z","receivedAt":"2019-12-23T18:50:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---\n t/t3433-rebase-options-compatibility.sh |   5 +-\n 2 files changed, 94 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex ff32ca1080..f1ace07c38 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used\n with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n-\tBehaves differently depending on which backend is selected.\n-+\n-'am' backend: When applying a patch, ignore changes in whitespace in\n-context lines if necessary.\n-+\n-'interactive' backend: Treat lines with only whitespace changes as\n-unchanged for the sake of a three-way merge.\n+\tIgnore whitespace-only changes in the commits being rebased,\n+\twhich may avoid \"unnecessary\" conflicts.  (Both backends\n+\tcurrently have differing edgecase bugs with this option; see\n+\tBEHAVIORAL DIFFERENCES.)\n \n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n-Directory rename heuristics are enabled in the merge and interactive\n-backends.  Due to the lack of accurate tree information, directory\n-rename detection is disabled in the am backend.\n+Due to the lack of accurate tree information (arising from\n+constructing fake ancestors with the limited information available in\n+patches), directory rename detection is disabled in the am backend.\n+Disabled directory rename detection means that if one side of history\n+renames a directory and the other adds new files to the old directory,\n+then the new files will be left behind in the old directory without\n+any warning at the time of rebasing that you may want to move these\n+files into the new directory.\n+\n+Directory rename detection works with the merge and interactive\n+backends to provide you warnings in such cases.\n+\n+Context\n+~~~~~~~\n+\n+The am backend works by creating a sequence of patches (by calling\n+`format-patch` internally), and then applying the patches in sequence\n+(calling `am` internally).  Patches are composed of multiple hunks,\n+each with line numbers, a context region, and the actual changes.  The\n+line numbers have to be taken with some fuzz, since the other side\n+will likely have inserted or deleted lines earlier in the file.  The\n+context region is meant to help find how to adjust the line numbers in\n+order to apply the changes to the right lines.  However, if multiple\n+areas of the code have the same surrounding lines of context, the\n+wrong one can be picked.  There are real-world cases where this has\n+caused commits to be reapplied incorrectly with no conflicts reported.\n+Setting diff.context to a larger value may prevent such types of\n+problems, but increases the chance of spurious conflicts (since it\n+will require more lines of matching context to apply).\n+\n+The interactive backend works with a full copy of each relevant file,\n+insulating it from these types of problems.\n+\n+Labelling of conflicts markers\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+When there are content conflicts, the merge machinery tries to\n+annotate each side's conflict markers with the commits where the\n+content came from.  Since the am backend drops the original\n+information about the rebased commits and their parents (and instead\n+generates new fake commits based off limited information in the\n+generated patches), those commits cannot be identified; instead it has\n+to fall back to a commit summary.  Also, when merge.conflictStyle is\n+set to diff3, the am backend will use \"constructed merge base\" to\n+label the content from the merge base, and thus provide no information\n+about the merge base commit whatsoever.\n+\n+The interactive backend works with the full commits on both sides of\n+history and thus has no such limitations.\n+\n+--ignore-whitespace\n+~~~~~~~~~~~~~~~~~~~\n+\n+The --ignore-whitespace option is supposed to ignore whitespace-only\n+changes if it allows the code to merge cleanly.  Unfortunately, the\n+different backends implement this differently, and both have different\n+edge case bugs.\n++\n+'am' backend: When applying a patch, ignore changes in whitespace in\n+context lines if necessary.  (Which implies that if the whitespace\n+change was not in the context lines but on a line with a real change,\n+then the rebase will still fail with \"unnecessary\" content conflicts.)\n++\n+'interactive' backend: Treat lines with only whitespace changes as\n+unchanged for the sake of a three-way merge.  This means that if one\n+side made no changes and the commits being rebased had whitespace-only\n+changes, those whitespaces fixups will be discarded despite the fact\n+that they present no content conflict.\n+\n+Miscellaneous differences\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+There are a few more behavioral differences that most folks would\n+probably consider inconsequential but which are mentioned for\n+completeness:\n+\n+* Reflog: The two backends will use different wording when describing\n+  the changes made in the reflog, though both will make use of the\n+  word \"rebase\".\n+\n+* Progress, informational, and error messages: The two backends\n+  provide slightly different progress and informational messages.\n+  Also, the am backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the interactive backend\n+  writes them to stderr.\n+\n+* State directories: The two backends keep their state in different\n+  directories under .git/\n \n include::merge-strategies.txt[]\n \ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex 5166f158dd..bd4d2d2f63 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe\n GIT_AUTHOR_DATE=\"1999-04-02T08:03:20+05:30\"\n export GIT_AUTHOR_DATE\n \n-# This is a special case in which both am and interactive backends\n-# provide the same output. It was done intentionally because\n-# both the backends fall short of optimal behaviour.\n+# This is a common case in which both am and interactive backends\n+# provide the same output with --ignore-whitespace.\n test_expect_success 'setup' '\n \tgit checkout -b topic &&\n \tq_to_tab >file <<-\\EOF &&\n-- \ngitgitgadget\n\n"},{"id":"388818","messageId":"ee26f5a161c2e21ee9a1723bd2851f323f93cb05.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 05/15] rebase: fix handling of restrict_revision","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:49Z","receivedAt":"2019-12-23T18:50:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nrestrict_revision in the original shell script was an excluded revision\nrange.  It is also treated that way by the am-backend.  In the\nconversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\nimplement the logic to initialize $revisions in C\", 2018-08-28)), the\ninteractive-backend accidentally treated it as a positive revision\nrather than a negated one.\n\nThis was missed as there were no tests in the testsuite that tested an\ninteractive rebase with fork-point behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  |  4 ++--\n t/t3400-rebase.sh | 20 +++++++++++++++++++-\n 2 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 5014c9a437..f1de5c8186 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \n \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n \tif (opts->restrict_revision)\n-\t\targv_array_push(&make_script_args,\n-\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n+\t\targv_array_pushf(&make_script_args, \"^%s\",\n+\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n \n \tret = sequencer_make_script(the_repository, &todo_list.buf,\n \t\t\t\t    make_script_args.argc, make_script_args.argv,\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 79762b989a..71fd6396cd 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n \tgit rebase master\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n \tgit checkout -b default-base master &&\n \tgit checkout -b default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual &&\n+\tgit checkout default-base &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout default &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+\tgit checkout -B default-base master &&\n+\tgit checkout -B default topic &&\n+\tgit config branch.default.remote . &&\n+\tgit config branch.default.merge refs/heads/default-base &&\n \tgit rebase &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n-- \ngitgitgadget\n\n"},{"id":"388827","messageId":"f2c92853b47dc5f661e9b20fd390bc6d823ad6d9.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 07/15] rebase: allow more types of rebases to fast-forward","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:51Z","receivedAt":"2019-12-23T18:50:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we dis-allowed rebases using the interactive backend from\nperforming a fast-forward to short-circuit the rebase operation.  This\nmade sense for explicitly interactive rebases and some implicitly\ninteractive rebases, but certainly became overly stringent when the\nmerge backend was re-implemented via the interactive backend.\n\nJust as the am-based rebase has always had to disable the fast-forward\nbased on a variety of conditions or flags (e.g. --signoff, --whitespace,\netc.), we need to do the same but now with a few more options.  However,\ncontinuing to use REBASE_FORCE for tracking this is problematic because\nthe interactive backend used it for a different purpose.  (When\nREBASE_FORCE wasn't set, the interactive backend would not fast-forward\nthe whole series but would fast-forward individual \"pick\" commits at the\nbeginning of the todo list, and then a squash or something would cause\nit to start generating new commits.)  So, introduce a new\nallow_preemptive_ff flag contained within cmd_rebase() and use it to\ntrack whether we are going to allow a pre-emptive fast-forward that\nshort-circuits the whole rebase.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c               | 18 ++++++++++++++----\n t/t3432-rebase-fast-forward.sh |  2 ++\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f1de5c8186..7027e34567 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1493,6 +1493,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n+\tint allow_preemptive_ff = 1;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t    options.ignore_date)\n \t\toptions.flags |= REBASE_FORCE;\n \n+\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n+\t    (action != ACTION_NONE) ||\n+\t    (exec.nr > 0) ||\n+\t    options.autosquash) {\n+\t\tallow_preemptive_ff = 0;\n+\t}\n+\n \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n \t\tconst char *option = options.git_am_opts.argv[i], *p;\n \t\tif (!strcmp(option, \"--whitespace=fix\") ||\n \t\t    !strcmp(option, \"--whitespace=strip\"))\n-\t\t\toptions.flags |= REBASE_FORCE;\n+\t\t\tallow_preemptive_ff = 0;\n \t\telse if (skip_prefix(option, \"-C\", &p)) {\n \t\t\twhile (*p)\n \t\t\t\tif (!isdigit(*(p++)))\n@@ -2144,12 +2152,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t/*\n \t * Check if we are already based on onto with linear history,\n \t * in which case we could fast-forward without replacing the commits\n-\t * with new commits recreated by replaying their changes. This\n-\t * optimization must not be done if this is an interactive rebase.\n+\t * with new commits recreated by replaying their changes.\n+\t *\n+\t * Note that can_fast_forward() initializes merge_base, so we have to\n+\t * call it before checking allow_preemptive_ff.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\t    allow_preemptive_ff) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 7432c0e241..40388ccf9f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -30,6 +30,8 @@ test_rebase_same_head () {\n \tshift &&\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\n \n test_rebase_same_head_ () {\n-- \ngitgitgadget\n\n"},{"id":"388825","messageId":"34a69def330c2d0fc00f34b0897604c55bed1514.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 06/15] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:50Z","receivedAt":"2019-12-23T18:50:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3432 had several stress tests for can_fast_forward(), whose intent was\nto ensure we were using the optimization of just fast forwarding when\npossible.  However, these tests verified that fast forwards had happened\nbased on the output that rebase printed to the terminal.  We can instead\ntest more directly that we actually fast-forwarded by checking the\nreflog, which also has the side effect of making the tests applicable\nfor the merge/interactive backend.\n\nThis change does lose the distinction between \"noop\" and \"noop-force\",\nbut as stated in commit c9efc216830f (\"t3432: test for --no-ff's\ninteraction with fast-forward\", 2019-08-27) which introduced that\ndistinction: \"These tests aren't supposed to endorse the status quo,\njust test for what we're currently doing.\".\n\nThis change does not actually run these tests with the merge/interactive\nbackend; instead this is just a preparatory commit.  A subsequent commit\nwhich fixes can_fast_forward() to work with that backend will then also\nchange t3432 to add tests of that backend as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------\n 1 file changed, 22 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 92f95b57da..7432c0e241 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,19 +44,15 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tcp .git/logs/HEAD expect &&\n \t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n-\t\t\t# Must check this case first, for 'is up to\n-\t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\told=\\$(wc -l <expect) &&\n+\t\t\ttest_line_count '-gt' \\$old .git/logs/HEAD\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout &&\n-\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n-\t\telif test $what = noop-force\n-\t\tthen\n-\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n+\t\t\ttest_cmp expect .git/logs/HEAD\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -71,14 +67,14 @@ test_rebase_same_head_ () {\n \n changes='no changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '\n \n changes='our changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head success noop same success work diff --keep-base master\n test_rebase_same_head success noop same success work diff --keep-base\n-- \ngitgitgadget\n\n"},{"id":"388820","messageId":"ec782e711c56673a70ed735980a3e4b74c45a61c.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 14/15] rebase: make the backend configurable via config setting","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:58Z","receivedAt":"2019-12-23T18:50:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt |  8 ++++++++\n builtin/rebase.c                | 31 ++++++++++++++++++++++++-------\n 2 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex d98e32d812..e6ae30c999 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -5,6 +5,14 @@ rebase.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+rebase.backend::\n+\tDefault backend to use for rebasing.  Possible choices are\n+\t'am' or 'merge' (note that the merge backend is sometimes also\n+\trefered to as the interactive backend or the interactive\n+\tmachinery elsewhere in the docs).  Also, in the future, if the\n+\tmerge backend gains all remaining capabilities of the am\n+\tbackend, this setting may become unused.\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b7915fc0cb..d602b2da4c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -60,6 +60,7 @@ enum empty_type {\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n+\tconst char *default_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -103,6 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n+\t\t.default_backend = \"am\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)\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+\n \treturn git_default_config(var, value, data);\n }\n \n@@ -1928,9 +1934,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n+\t\tif (i >= 0) {\n+\t\t\tif (is_interactive(&options))\n+\t\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t\t      \"interactive or merge options\"));\n+\t\t\telse\n+\t\t\t\toptions.type = REBASE_AM;\n+\t\t}\n+\t}\n+\n+\tif (options.type == REBASE_UNSPECIFIED) {\n+\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"am\"))\n+\t\t\toptions.type = REBASE_AM;\n+\t\telse\n+\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n+\t\t\t    options.default_backend);\n \t}\n \n \tswitch (options.type) {\n@@ -1943,10 +1963,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n-\t\t/* the default rebase backend is `--am` */\n-\t\toptions.type = REBASE_AM;\n-\t\toptions.state_dir = apply_dir();\n-\t\tbreak;\n+\t\tBUG(\"options.type was just set above; should be unreachable.\");\n \t}\n \n \tif (options.empty == EMPTY_UNSPECIFIED) {\n-- \ngitgitgadget\n\n"},{"id":"388822","messageId":"02878813611384791af5f53d06372f0f1b9575e9.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 13/15] rebase tests: repeat some tests using the merge backend instead of am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:57Z","receivedAt":"2019-12-23T18:50:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn order to ensure the merge/interactive backend gets similar coverage\nto the am one, add some tests for cases where previously only the am\nbackend was tested.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t5520-pull.sh                   | 17 +++++++++++++++--\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--\n 2 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 602d996a33..3fff6a06fa 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -277,14 +277,27 @@ test_expect_success '--rebase' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--rebase fast forward' '\n+test_expect_success '--rebase (merge) fast forward' '\n \tgit reset --hard before-rebase &&\n \tgit checkout -b ff &&\n \techo another modification >file &&\n \tgit commit -m third file &&\n \n \tgit checkout to-rebase &&\n-\tgit pull --rebase . ff &&\n+\tgit -c rebase.backend=merge pull --rebase . ff &&\n+\ttest_cmp_rev HEAD ff &&\n+\n+\t# The above only validates the result.  Did we actually bypass rebase?\n+\tgit reflog -1 >reflog.actual &&\n+\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\ttest_cmp reflog.expected reflog.fuzzy\n+'\n+\n+test_expect_success '--rebase (am) fast forward' '\n+\tgit reset --hard before-rebase &&\n+\n+\tgit -c rebase.backend=am pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 860542aad0..d383ce8130 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n-test_expect_success 'rebase describes fake ancestor base' '\n+test_expect_success 'rebase --merge describes parent of commit being picked' '\n \ttest_create_repo rebase &&\n \t(\n \t\tcd rebase &&\n@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '\n \t\ttest_commit master file &&\n \t\tgit checkout -b side HEAD^ &&\n \t\ttest_commit side file &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&\n+\t\tgrep \"||||||| parent of\" file\n+\t)\n+'\n+\n+test_expect_success 'rebase --am describes fake ancestor base' '\n+\t(\n+\t\tcd rebase &&\n+\t\tgit rebase --abort &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"388824","messageId":"c905d288bf94f8326f01dedfd432d1a91cb040bf.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 12/15] rebase tests: mark tests specific to the am-backend with --am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:56Z","receivedAt":"2019-12-23T18:50:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have many rebase tests in the testsuite, and often the same test is\nrepeated multiple times just testing different backends.  For those\ntests that were specifically trying to test the am backend, add the --am\nflag.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3400-rebase.sh                       | 10 +++++-----\n t/t3401-rebase-and-am-rename.sh         |  4 ++--\n t/t3404-rebase-interactive.sh           |  2 +-\n t/t3406-rebase-message.sh               | 12 ++++++------\n t/t3407-rebase-abort.sh                 |  6 +++---\n t/t3420-rebase-autostash.sh             |  2 +-\n t/t3425-rebase-topology-merges.sh       |  8 ++++----\n t/t3432-rebase-fast-forward.sh          |  4 ++--\n t/t3433-rebase-options-compatibility.sh |  8 ++++----\n t/t5407-post-rewrite-hook.sh            | 12 ++++++------\n t/t7512-status-help.sh                  | 12 ++++++------\n 11 files changed, 40 insertions(+), 40 deletions(-)\n\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 71fd6396cd..0a491f2363 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \n test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase -q master >output.out 2>&1 &&\n+\tgit rebase --am -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex a0b9438b22..50803958fd 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase (am): directory rename detected' '\n+test_expect_failure 'rebase --am: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase A &&\n+\t\tgit -c merge.directoryRenames=true rebase --am A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ae6e55ce79..743b7e511a 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 0c2c569f95..7ce617fc1f 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase master >out &&\n+\tgit rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase master >out &&\n+\tgit rebase --force-rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase master topic >out &&\n+\tgit rebase --am master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase topic >out &&\n+\tgit rebase --am topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase reflog-onto &&\n+\tgit rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 910f218284..3e31826170 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --quit' '\n+test_expect_success 'rebase --am --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase master &&\n+\ttest_must_fail git rebase --am master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 5f7e73cf83..3816159e20 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex fd8efe84fe..19700b025b 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' ''\n+test_run_rebase success 'n o e' --am\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 40388ccf9f..4b3cecce56 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex bd4d2d2f63..a07e1f276b 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '\n \tnew line 2\n \tline 3\n \tEOF\n-\ttest_must_fail git rebase main side &&\n+\ttest_must_fail git rebase --am main side &&\n \tgit rebase --abort &&\n-\tgit rebase --ignore-whitespace main side &&\n+\tgit rebase --am --ignore-whitespace main side &&\n \ttest_cmp expect file\n '\n \n@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '\n \n test_expect_success '--committer-date-is-author-date works with am backend' '\n \tgit commit --amend &&\n-\tgit rebase --committer-date-is-author-date HEAD^ &&\n+\tgit rebase --am --committer-date-is-author-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgit show HEAD --pretty=\"format:%ci\" >committertime &&\n \ttest_cmp authortime committertime\n@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '\n # sets to +0530.\n test_expect_success '--ignore-date works with am backend' '\n \tgit commit --amend --date=\"$GIT_AUTHOR_DATE\" &&\n-\tgit rebase --ignore-date HEAD^ &&\n+\tgit rebase --am --ignore-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgrep \"+0000\" authortime\n '\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 7344253bfb..a8a73616e4 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase' '\n+test_expect_success 'git rebase --am' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip' '\n+test_expect_success 'git rebase --am --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip the last one' '\n+test_expect_success 'git rebase --am --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto D A &&\n+\ttest_must_fail git rebase --am --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 66d7a62797..d22b0acf2a 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase in progress before rebase --continue' '\n+test_expect_success 'status when rebase --am in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\n-- \ngitgitgadget\n\n"},{"id":"388821","messageId":"7adcbc0bc5822ff91b8a0021adaf1382aff582b2.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:59Z","receivedAt":"2019-12-23T18:50:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe am-backend drops information and thus limits what we can do:\n\n  * lack of full tree information from the original commits means we\n    cannot do directory rename detection and warn users that they might\n    want to move some of their new files that they placed in old\n    directories to prevent their becoming orphaned.[1]\n  * reduction in context from only having a few lines beyond those\n    changed means that when context lines are non-unique we can apply\n    patches incorrectly.[2]\n  * lack of access to original commits means that conflict marker\n    annotation has less information available.\n\nAlso, the merge/interactive backend have far more abilities, appear to\ncurrently have a slight performance advantage[3] and have room for more\noptimizations than the am backend[4] (and work is underway to take\nadvantage of some of those possibilities).\n\n[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt           |  2 +-\n builtin/rebase.c                       |  4 ++--\n t/t5520-pull.sh                        | 10 ++++++----\n t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n 4 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex cf1ac2e359..e819889a31 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.\n --merge::\n \tUse merging strategies to rebase.  When the recursive (default) merge\n \tstrategy is used, this allows rebase to be aware of renames on the\n-\tupstream side.\n+\tupstream side.  This is the default.\n +\n Note that a rebase merge works by replaying each commit from the working\n branch on top of the <upstream> branch.  Because of this, when a merge\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex d602b2da4c..938a8840b1 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -104,7 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"am\",\t  \t\\\n+\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1945,7 +1945,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\toptions.type = REBASE_MERGE;\n+\t\t\timply_interactive(&options, \"--merge\");\n \t\telse if (!strcmp(options.default_backend, \"am\"))\n \t\t\toptions.type = REBASE_AM;\n \t\telse\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 3fff6a06fa..4f9e7f7ff6 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '\n \ttest_tick &&\n \tgit commit -m \"Create conflict\" seq.txt &&\n \ttest_must_fail git pull --rebase . seq 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success 'failed --rebase shows advice' '\n@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '\n \tgit checkout -f -b fails-to-rebase HEAD^ &&\n \ttest_commit v2-without-cr file \"2\" file2-lf &&\n \ttest_must_fail git pull --rebase . diverging 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success '--rebase fails with multiple branches' '\n@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull --rebase &&\n-\t\tfind .git/rebase-apply -name \"000*\" >patches &&\n-\t\ttest_line_count = 1 patches\n+\t\tcat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&\n+\t\tgrep -v -e \\# -e ^$ work >patches &&\n+\t\ttest_line_count = 1 patches &&\n+\t\trm -f work\n \t)\n '\n \ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex dbe8deac0d..aec45bca3b 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' \"\n \n \n test_expect_success 'check that rebase really failed' '\n-\ttest -d .git/rebase-apply\n+\tgit status >output &&\n+\tgrep currently.rebasing output\n '\n \n test_expect_success 'resolve, continue the rebase and dcommit' \"\n-- \ngitgitgadget\n"},{"id":"388823","messageId":"7c3f2e07f3d388730f2a2ba48c70ad1590a91d99.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 09/15] rebase: move incompatibility checks between backend options a bit earlier","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:53Z","receivedAt":"2019-12-23T18:50:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7027e34567..d2b99e9908 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1906,6 +1906,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n+\tif (options.git_am_opts.argc) {\n+\t\t/* all am options except -q are compatible only with --am */\n+\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n+\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n+\t\t\t\tbreak;\n+\n+\t\tif (is_interactive(&options) && i >= 0)\n+\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t      \"interactive or merge options\"));\n+\t}\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_INTERACTIVE:\n@@ -1936,17 +1947,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (reschedule_failed_exec >= 0)\n \t\toptions.reschedule_failed_exec = reschedule_failed_exec;\n \n-\tif (options.git_am_opts.argc) {\n-\t\t/* all am options except -q are compatible only with --am */\n-\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n-\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-\t\t\t\tbreak;\n-\n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n-\t}\n-\n \tif (options.signoff) {\n \t\tif (options.type == REBASE_PRESERVE_MERGES)\n \t\t\tdie(\"cannot combine '--signoff' with \"\n-- \ngitgitgadget\n\n"},{"id":"388826","messageId":"1df11f0b5105b1f602fdd723e0f74565e436faba.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 10/15] rebase: add an --am option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:54Z","receivedAt":"2019-12-23T18:50:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCurrently, this option doesn't do anything except error out if any\noptions requiring the interactive-backend are also passed.  However,\nwhen we make the default backend configurable later in this series, this\nflag will provide a way to override the config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 11 ++++++++++-\n builtin/rebase.c             | 18 +++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex f1ace07c38..cf1ac2e359 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--am:\n+\tUse git-am internally to rebase.  This option may become a\n+\tno-op in the future once the interactive backend handles\n+\teverything the am one does.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --empty={drop,keep,ask}::\n \tHow to handle commits that become empty (because they contain a\n \tsubset of already upstream changes) or start empty.  With drop\n@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.\n+\tever ignored.  Implies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n+\tImplies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n+ * --am\n  * --whitespace\n  * -C\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex d2b99e9908..b7915fc0cb 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \treturn res && is_linear_history(onto, head);\n }\n \n+static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_AM;\n+\n+\treturn 0;\n+}\n+\n /* -i followed by -m is still -i */\n static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n {\n@@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n+\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n+\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n \t\t\tN_(\"use merging strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n@@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc) {\n+\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n \t\t/* all am options except -q are compatible only with --am */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-- \ngitgitgadget\n\n"},{"id":"388828","messageId":"94b5a3051d743e8f54c21bc4cd0413f3c0dd05fa.1577127000.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v2 11/15] contrib: change the prompt for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-23T18:49:55Z","receivedAt":"2019-12-23T18:50:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we had different prompts for different types of rebases:\n   REBASE: for am-based rebases\n   REBASE-m: for merge-based rebases\n   REBASE-i: for interactive-based rebases\n\nIt's not clear why this distinction was necessary or helpful; when the\nprompt was added in commit e75201963f67 (\"Improve bash prompt to detect\nvarious states like an unfinished merge\", 2007-09-30), it simply added\nthese three different types.  Perhaps there was a useful purpose back\nthen, but there have been some changes:\n\n  * The merge backend was deleted after being implemented on top of the\n    interactive backend, causing the prompt for merge-based rebases to\n    change from REBASE-m to REBASE-i.\n  * The interactive backend is used for multiple different types of\n    non-interactive rebases, so the \"-i\" part of the prompt doesn't\n    really mean what it used to.\n  * Rebase backends have gained more abilities and have a great deal of\n    overlap, sometimes making it hard to distinguish them.\n  * Behavioral differences between the backends have also been ironed\n    out.\n  * We want to change the default backend from am to interactive, which\n    means people would get \"REBASE-i\" by default if we didn't change\n    the prompt, and only if they specified --am or --whitespace or -C\n    would they get the \"REBASE\" prompt.\n  * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n    run the interactive backend once it can handle everything the\n    am-backend can.\n\nFor all these reasons, make the prompt for any type of rebase just be\n\"REBASE\".\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n contrib/completion/git-prompt.sh | 4 ++--\n t/t9903-bash-prompt.sh           | 8 ++++----\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\nindex 1d510cd47b..8f8a22ba60 100644\n--- a/contrib/completion/git-prompt.sh\n+++ b/contrib/completion/git-prompt.sh\n@@ -430,9 +430,9 @@ __git_ps1 ()\n \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n \t\t__git_eread \"$g/rebase-merge/end\" total\n \t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n-\t\t\tr=\"|REBASE-i\"\n+\t\t\tr=\"|REBASE\"\n \t\telse\n-\t\t\tr=\"|REBASE-m\"\n+\t\t\tr=\"|REBASE\"\n \t\tfi\n \telse\n \t\tif [ -d \"$g/rebase-apply\" ]; then\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 88bc733ad6..7ca35d358d 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '\n '\n \n test_expect_success 'prompt - interactive rebase' '\n-\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n+\tprintf \" (b1|REBASE 2/3)\" >expected &&\n \twrite_script fake_editor.sh <<-\\EOF &&\n \t\techo \"exec echo\" >\"$1\"\n \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '\n '\n \n test_expect_success 'prompt - rebase merge' '\n-\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n+\tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n \ttest_must_fail git rebase --merge b1 b2 &&\n@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n \ttest_cmp expected \"$actual\"\n '\n \n-test_expect_success 'prompt - rebase' '\n+test_expect_success 'prompt - rebase am' '\n \tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase b1 b2 &&\n+\ttest_must_fail git rebase --am b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n\n"},{"id":"388833","messageId":"20191223220024.GA2152949@generichostname","threadId":"52496","inReplyTo":"94b5a3051d743e8f54c21bc4cd0413f3c0dd05fa.1577127000.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-12-23T22:00:24Z","receivedAt":"2019-12-23T22:00:32Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Elijah,\n\n> Subject: contrib: change the prompt for interactive-based rebases\n\nI'll also echo Gábor's comments and suggest that you use \"git-prompt\"\nfor the change area.\n\nOn Mon, Dec 23, 2019 at 06:49:55PM +0000, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> In the past, we had different prompts for different types of rebases:\n>    REBASE: for am-based rebases\n>    REBASE-m: for merge-based rebases\n>    REBASE-i: for interactive-based rebases\n> \n> It's not clear why this distinction was necessary or helpful; when the\n> prompt was added in commit e75201963f67 (\"Improve bash prompt to detect\n> various states like an unfinished merge\", 2007-09-30), it simply added\n> these three different types.  Perhaps there was a useful purpose back\n> then, but there have been some changes:\n> \n>   * The merge backend was deleted after being implemented on top of the\n>     interactive backend, causing the prompt for merge-based rebases to\n>     change from REBASE-m to REBASE-i.\n>   * The interactive backend is used for multiple different types of\n>     non-interactive rebases, so the \"-i\" part of the prompt doesn't\n>     really mean what it used to.\n>   * Rebase backends have gained more abilities and have a great deal of\n>     overlap, sometimes making it hard to distinguish them.\n>   * Behavioral differences between the backends have also been ironed\n>     out.\n>   * We want to change the default backend from am to interactive, which\n>     means people would get \"REBASE-i\" by default if we didn't change\n>     the prompt, and only if they specified --am or --whitespace or -C\n>     would they get the \"REBASE\" prompt.\n>   * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n>     run the interactive backend once it can handle everything the\n>     am-backend can.\n> \n> For all these reasons, make the prompt for any type of rebase just be\n> \"REBASE\".\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  contrib/completion/git-prompt.sh | 4 ++--\n>  t/t9903-bash-prompt.sh           | 8 ++++----\n>  2 files changed, 6 insertions(+), 6 deletions(-)\n> \n> diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\n> index 1d510cd47b..8f8a22ba60 100644\n> --- a/contrib/completion/git-prompt.sh\n> +++ b/contrib/completion/git-prompt.sh\n> @@ -430,9 +430,9 @@ __git_ps1 ()\n>  \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n>  \t\t__git_eread \"$g/rebase-merge/end\" total\n>  \t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n> -\t\t\tr=\"|REBASE-i\"\n> +\t\t\tr=\"|REBASE\"\n>  \t\telse\n> -\t\t\tr=\"|REBASE-m\"\n> +\t\t\tr=\"|REBASE\"\n\nWe should just drop the if here since both arms are the same..\n\nThanks,\n\nDenton\n\n>  \t\tfi\n>  \telse\n>  \t\tif [ -d \"$g/rebase-apply\" ]; then\n> diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\n> index 88bc733ad6..7ca35d358d 100755\n> --- a/t/t9903-bash-prompt.sh\n> +++ b/t/t9903-bash-prompt.sh\n> @@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '\n>  '\n>  \n>  test_expect_success 'prompt - interactive rebase' '\n> -\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n> +\tprintf \" (b1|REBASE 2/3)\" >expected &&\n>  \twrite_script fake_editor.sh <<-\\EOF &&\n>  \t\techo \"exec echo\" >\"$1\"\n>  \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n> @@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '\n>  '\n>  \n>  test_expect_success 'prompt - rebase merge' '\n> -\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n> +\tprintf \" (b2|REBASE 1/3)\" >expected &&\n>  \tgit checkout b2 &&\n>  \ttest_when_finished \"git checkout master\" &&\n>  \ttest_must_fail git rebase --merge b1 b2 &&\n> @@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n>  \ttest_cmp expected \"$actual\"\n>  '\n>  \n> -test_expect_success 'prompt - rebase' '\n> +test_expect_success 'prompt - rebase am' '\n>  \tprintf \" (b2|REBASE 1/3)\" >expected &&\n>  \tgit checkout b2 &&\n>  \ttest_when_finished \"git checkout master\" &&\n> -\ttest_must_fail git rebase b1 b2 &&\n> +\ttest_must_fail git rebase --am b1 b2 &&\n>  \ttest_when_finished \"git rebase --abort\" &&\n>  \t__git_ps1 >\"$actual\" &&\n>  \ttest_cmp expected \"$actual\"\n> -- \n> gitgitgadget\n> \n"},{"id":"388876","messageId":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v2.git.git.1577126999.gitgitgadget@gmail.com","subject":"[PATCH v3 00/15] rebase: make the default backend configurable","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:44Z","receivedAt":"2019-12-24T19:55:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series does a lot of work around making the default rebase backend\nconfigurable, and switching the default from the am backend to the\nmerge/interactive one.\n\nSee the cover letter for v1[*] for a more detailed motivation for the\nseries, the type of changes being made, and areas I'd like to reviewers to\nfocus on.\n\nChanges since v2:\n\n * Tweaked patch 11 (commit message, and condensing if-else block that was\n   same on both sides), as suggested by Denton\n\n[*] \nhttps://lore.kernel.org/git/pull.679.git.git.1576861788.gitgitgadget@gmail.com/\n\nElijah Newren (15):\n  rebase: extend the options for handling of empty commits\n  t3406: simplify an already simple test\n  rebase, sequencer: remove the broken GIT_QUIET handling\n  rebase: make sure to pass along the quiet flag to the sequencer\n  rebase: fix handling of restrict_revision\n  t3432: make these tests work with either am or merge backends\n  rebase: allow more types of rebases to fast-forward\n  git-rebase.txt: add more details about behavioral differences of\n    backends\n  rebase: move incompatibility checks between backend options a bit\n    earlier\n  rebase: add an --am option\n  git-prompt: change the prompt for interactive-based rebases\n  rebase tests: mark tests specific to the am-backend with --am\n  rebase tests: repeat some tests using the merge backend instead of am\n  rebase: make the backend configurable via config setting\n  rebase: change the default backend from \"am\" to \"merge\"\n\n Documentation/config/rebase.txt         |   8 ++\n Documentation/git-rebase.txt            | 150 ++++++++++++++++----\n builtin/rebase.c                        | 177 +++++++++++++++++++-----\n contrib/completion/git-prompt.sh        |   6 +-\n rebase-interactive.c                    |   4 +-\n rebase-interactive.h                    |   2 +-\n sequencer.c                             |  80 ++++++++---\n sequencer.h                             |   6 +-\n t/t3400-rebase.sh                       |  36 ++++-\n t/t3401-rebase-and-am-rename.sh         |   4 +-\n t/t3404-rebase-interactive.sh           |   2 +-\n t/t3406-rebase-message.sh               |  19 ++-\n t/t3407-rebase-abort.sh                 |   6 +-\n t/t3420-rebase-autostash.sh             |   2 +-\n t/t3421-rebase-topology-linear.sh       |   4 +-\n t/t3424-rebase-empty.sh                 |  89 ++++++++++++\n t/t3425-rebase-topology-merges.sh       |   8 +-\n t/t3427-rebase-subtree.sh               |  16 ++-\n t/t3432-rebase-fast-forward.sh          |  54 ++++----\n t/t3433-rebase-options-compatibility.sh |  13 +-\n t/t5407-post-rewrite-hook.sh            |  12 +-\n t/t5520-pull.sh                         |  27 +++-\n t/t6047-diff3-conflict-markers.sh       |  13 +-\n t/t7512-status-help.sh                  |  12 +-\n t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-\n t/t9903-bash-prompt.sh                  |   8 +-\n 26 files changed, 576 insertions(+), 185 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\n\nbase-commit: 12029dc57db23baef008e77db1909367599210ee\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v3\nPull-Request: https://github.com/git/git/pull/679\n\nRange-diff vs v2:\n\n  1:  1c2b77e94d =  1:  1c2b77e94d rebase: extend the options for handling of empty commits\n  2:  bd3c5ec155 =  2:  bd3c5ec155 t3406: simplify an already simple test\n  3:  49388b79fd =  3:  49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling\n  4:  478479358f =  4:  478479358f rebase: make sure to pass along the quiet flag to the sequencer\n  5:  ee26f5a161 =  5:  ee26f5a161 rebase: fix handling of restrict_revision\n  6:  34a69def33 =  6:  34a69def33 t3432: make these tests work with either am or merge backends\n  7:  f2c92853b4 =  7:  f2c92853b4 rebase: allow more types of rebases to fast-forward\n  8:  b307340f7c =  8:  b307340f7c git-rebase.txt: add more details about behavioral differences of backends\n  9:  7c3f2e07f3 =  9:  7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier\n 10:  1df11f0b51 = 10:  1df11f0b51 rebase: add an --am option\n 11:  94b5a3051d ! 11:  ff43593211 contrib: change the prompt for interactive-based rebases\n     @@ -1,6 +1,6 @@\n      Author: Elijah Newren <newren@gmail.com>\n      \n     -    contrib: change the prompt for interactive-based rebases\n     +    git-prompt: change the prompt for interactive-based rebases\n      \n          In the past, we had different prompts for different types of rebases:\n             REBASE: for am-based rebases\n     @@ -40,17 +40,18 @@\n       --- a/contrib/completion/git-prompt.sh\n       +++ b/contrib/completion/git-prompt.sh\n      @@\n     + \t\t__git_eread \"$g/rebase-merge/head-name\" b\n       \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n       \t\t__git_eread \"$g/rebase-merge/end\" total\n     - \t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n     +-\t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n      -\t\t\tr=\"|REBASE-i\"\n     -+\t\t\tr=\"|REBASE\"\n     - \t\telse\n     +-\t\telse\n      -\t\t\tr=\"|REBASE-m\"\n     -+\t\t\tr=\"|REBASE\"\n     - \t\tfi\n     +-\t\tfi\n     ++\t\tr=\"|REBASE\"\n       \telse\n       \t\tif [ -d \"$g/rebase-apply\" ]; then\n     + \t\t\t__git_eread \"$g/rebase-apply/next\" step\n      \n       diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\n       --- a/t/t9903-bash-prompt.sh\n 12:  c905d288bf = 12:  99388f24e5 rebase tests: mark tests specific to the am-backend with --am\n 13:  0287881361 = 13:  c2ba6317bf rebase tests: repeat some tests using the merge backend instead of am\n 14:  ec782e711c = 14:  8bec6df51a rebase: make the backend configurable via config setting\n 15:  7adcbc0bc5 = 15:  044853fd61 rebase: change the default backend from \"am\" to \"merge\"\n\n-- \ngitgitgadget\n"},{"id":"388875","messageId":"bd3c5ec155325d78f89afcbe910810cc18c0aaeb.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 02/15] t3406: simplify an already simple test","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:46Z","receivedAt":"2019-12-24T19:55:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen the merge backend was re-implemented on top of the interactive\nbackend, the output of rebase --merge changed a little.  This change\nallowed this test to be simplified, though it wasn't noticed until now.\nSimplify the testcase a little.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3406-rebase-message.sh | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex b393e1e9fe..0c2c569f95 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -18,11 +18,8 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'rebase -m' '\n-\tgit rebase -m master >report &&\n-\t>expect &&\n-\tsed -n -e \"/^Already applied: /p\" \\\n-\t\t-e \"/^Committed: /p\" report >actual &&\n-\ttest_cmp expect actual\n+\tgit rebase -m master >actual &&\n+\ttest_must_be_empty actual\n '\n \n test_expect_success 'rebase against master twice' '\n-- \ngitgitgadget\n\n"},{"id":"388877","messageId":"49388b79fddc339f3a5df747406621ec5ad28789.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 03/15] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:47Z","receivedAt":"2019-12-24T19:55:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe GIT_QUIET environment variable was used to signal the non-am\nbackends that the rebase should perform quietly.  The preserve-merges\nbackend does not make use of the quiet flag anywhere (other than to\nwrite out its state whenever it writes state), and this mechanism was\nbroken in the conversion from shell to C.  Since this environment\nvariable was specifically designed for scripts and the only backend that\nwould still use it is no longer a script, just gut this code.\n\nA subsequent commit will fix --quiet for the interactive/merge backend\nin a different way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 6 ++----\n sequencer.c      | 6 ++----\n 2 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 6903249307..32026a62e8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t   opts->onto ? oid_to_hex(&opts->onto->object.oid) : \"\");\n \twrite_file(state_dir_path(\"orig-head\", opts), \"%s\",\n \t\t   oid_to_hex(&opts->orig_head));\n-\twrite_file(state_dir_path(\"quiet\", opts), \"%s\",\n-\t\t   opts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n+\tif (!(opts->flags & REBASE_NO_QUIET))\n+\t\twrite_file(state_dir_path(\"quiet\", opts), \"%s\", \"\");\n \tif (opts->flags & REBASE_VERBOSE)\n \t\twrite_file(state_dir_path(\"verbose\", opts), \"%s\", \"\");\n \tif (opts->strategy)\n@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"revisions\", opts->revisions);\n \tadd_var(&script_snippet, \"restrict_revision\", opts->restrict_revision ?\n \t\toid_to_hex(&opts->restrict_revision->object.oid) : NULL);\n-\tadd_var(&script_snippet, \"GIT_QUIET\",\n-\t\topts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n \tsq_quote_argv_pretty(&buf, opts->git_am_opts.argv);\n \tadd_var(&script_snippet, \"git_am_opt\", buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/sequencer.c b/sequencer.c\nindex d2c11f34b7..71062212a5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)\n int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\t      struct commit *onto, const char *orig_head)\n {\n-\tconst char *quiet = getenv(\"GIT_QUIET\");\n-\n \tif (head_name)\n \t\twrite_file(rebase_path_head_name(), \"%s\\n\", head_name);\n \tif (onto)\n@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \tif (orig_head)\n \t\twrite_file(rebase_path_orig_head(), \"%s\\n\", orig_head);\n \n-\tif (quiet)\n-\t\twrite_file(rebase_path_quiet(), \"%s\\n\", quiet);\n+\tif (opts->quiet)\n+\t\twrite_file(rebase_path_quiet(), \"%s\", \"\");\n \tif (opts->verbose)\n \t\twrite_file(rebase_path_verbose(), \"%s\", \"\");\n \tif (opts->strategy)\n-- \ngitgitgadget\n\n"},{"id":"388878","messageId":"1c2b77e94d63f86590ca934855066eca278f576e.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:45Z","receivedAt":"2019-12-24T19:55:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nExtend the interactive machinery with the ability to handle the full\nspread of options for how to handle commits that either start or become\nempty (by \"become empty\" I mean the changes in a commit are a subset of\nchanges that exist upstream, so the net effect of applying the commit is\nno changes).  Introduce a new command line flag for selecting the\ndesired behavior:\n    --empty={drop,keep,ask}\nwith the definitions:\n    drop: drop empty commits\n    keep: keep empty commits\n    ask:  provide the user a chance to interact and pick what to do with\n          empty commits on a case-by-case basis\n\nNote that traditionally, am-based rebases have always dropped commits\nthat either started or became empty, while interactive-based rebases\nhave defaulted to ask (and provided an option to keep commits that\nstarted empty).  This difference made sense since users of an am-based\nrebase just wanted to quickly batch apply a sequence of commits, while\nusers editing a todo list will likely want the chance to interact and\nhandle unusual cases on a case-by-case basis.  However, not all rebases\nusing the interactive machinery are explicitly interactive anymore.  In\nparticular --merge was always meant to behave more like --am: just\nrebase a batch of commits without popping up a todo list.\n\nIf the --empty flag is not specified, pick defaults as follows:\n    explicitly interactive: ask\n    --exec: keep (exec is about checking existing commits, and often\n                  used without actually changing the base.  Thus the\n                  expectation is that the user doesn't necessarily want\n                  anything to change; they just want to test).\n    otherwise: drop\n\nAlso, this commit makes --keep-empty just imply --empty=keep, and hides\nit from help so that we aren't confusing users with different ways to do\nthe same thing.  (I could have added a --drop-empty flag, but then that\ninvites users to specify both --keep-empty and --drop-empty and we have\nto add sanity checking around that; it seems cleaner to have a single\nmulti-valued option.)  This actually fixes --keep-empty too; previously,\nit only meant to sometimes keep empty commits, in particular commits\nwhich started empty would be kept.  But it would still error out and ask\nthe user what to do with commits that became empty.  Now it keeps empty\ncommits, as instructed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt      | 35 ++++++------\n builtin/rebase.c                  | 83 +++++++++++++++++++++++++---\n rebase-interactive.c              |  4 +-\n rebase-interactive.h              |  2 +-\n sequencer.c                       | 74 +++++++++++++++++++------\n sequencer.h                       |  6 ++-\n t/t3421-rebase-topology-linear.sh |  4 +-\n t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++\n t/t3427-rebase-subtree.sh         | 16 +++---\n 9 files changed, 263 insertions(+), 50 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d0e2d27cc..ff32ca1080 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--empty={drop,keep,ask}::\n+\tHow to handle commits that become empty (because they contain a\n+\tsubset of already upstream changes) or start empty.  With drop\n+\t(the default), commits that start or become empty are dropped.\n+\tWith keep (implied by --exec), such commits are kept.  With ask\n+\t(implied by --interactive), the rebase will halt when an empty\n+\tcommit is applied allowing you to choose whether to drop it or\n+\tcommit it.  Also with ask, if the rebase is interactive then\n+\tcommits which start empty will be commented out in the todo\n+\taction list (giving you a chance to uncomment).\n++\n+Note that this has no effect on commits which are already upstream (as\n+can be checked via `git log --cherry-mark ...`), which are always\n+dropped by rebase.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --keep-empty::\n-\tKeep the commits that do not change anything from its\n-\tparents in the result.\n+\tDeprecated alias for what is now known as --empty=keep.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -569,6 +585,7 @@ are incompatible with the following options:\n  * --interactive\n  * --exec\n  * --keep-empty\n+ * --empty=\n  * --edit-todo\n  * --root when used in combination with --onto\n \n@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --ignore-whitespace\n  * --preserve-merges and --committer-date-is-author-date\n  * --preserve-merges and --ignore-date\n+ * --preserve-merges and --empty=\n  * --keep-base and --onto\n  * --keep-base and --root\n \n@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES\n \n There are some subtle differences how the backends behave.\n \n-Empty commits\n-~~~~~~~~~~~~~\n-\n-The am backend drops any \"empty\" commits, regardless of whether the\n-commit started empty (had no changes relative to its parent to\n-start with) or ended empty (all changes were already applied\n-upstream in other commits).\n-\n-The interactive backend drops commits by default that\n-started empty and halts if it hits a commit that ended up empty.\n-The `--keep-empty` option exists for the interactive backend to allow\n-it to keep commits that started empty.\n-\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ddf33bc9d4..6903249307 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -50,8 +50,16 @@ enum rebase_type {\n \tREBASE_PRESERVE_MERGES\n };\n \n+enum empty_type {\n+\tEMPTY_UNSPECIFIED = -1,\n+\tEMPTY_DROP,\n+\tEMPTY_KEEP,\n+\tEMPTY_ASK\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n+\tenum empty_type empty;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -77,7 +85,6 @@ struct rebase_options {\n \tconst char *action;\n \tint signoff;\n \tint allow_rerere_autoupdate;\n-\tint keep_empty;\n \tint autosquash;\n \tint ignore_whitespace;\n \tchar *gpg_sign_opt;\n@@ -95,6 +102,7 @@ struct rebase_options {\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n \treplay.allow_empty = 1;\n \treplay.allow_empty_message = opts->allow_empty_message;\n+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n+\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n+\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \n \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n \n-\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n+\tflags |= (opts->empty == EMPTY_ASK &&\n+\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n+\t\t\tTODO_LIST_ASK_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \treturn ret;\n }\n \n+static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->empty = EMPTY_KEEP;\n+\topts->type = REBASE_INTERACTIVE;\n+\treturn 0;\n+}\n+\n static const char * const builtin_rebase_interactive_usage[] = {\n \tN_(\"git rebase--interactive [<options>]\"),\n \tNULL\n@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n-\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\t N_(\"allow commits with empty messages\")),\n \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\topts->allow_rerere_autoupdate ?\n \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n-\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n+\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n@@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static enum empty_type parse_empty_value(const char *value)\n+{\n+\tif (!strcasecmp(value, \"drop\"))\n+\t\treturn EMPTY_DROP;\n+\telse if (!strcasecmp(value, \"keep\"))\n+\t\treturn EMPTY_KEEP;\n+\telse if (!strcasecmp(value, \"ask\"))\n+\t\treturn EMPTY_ASK;\n+\n+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n+}\n+\n+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\tenum empty_type value = parse_empty_value(arg);\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\toptions->empty = value;\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@@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n-\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n-\t\t\t N_(\"preserve empty commits during rebase\")),\n+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n+\t\t\t       N_(\"how to handle empty commits\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n@@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n-\tif (options.keep_empty)\n-\t\timply_interactive(&options, \"--keep-empty\");\n+\tif (options.empty != EMPTY_UNSPECIFIED)\n+\t\timply_interactive(&options, \"--empty\");\n \n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n@@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \t}\n \n+\tif (options.empty == EMPTY_UNSPECIFIED) {\n+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n+\t\t\toptions.empty = EMPTY_ASK;\n+\t\telse if (exec.nr > 0)\n+\t\t\toptions.empty = EMPTY_KEEP;\n+\t\telse\n+\t\t\toptions.empty = EMPTY_DROP;\n+\t}\n \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad82bf77df 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n \treturn MISSING_COMMIT_CHECK_IGNORE;\n }\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf)\n {\n@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,\n \n \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \n-\tif (!keep_empty) {\n+\tif (!no_ask_empty) {\n \t\tmsg = _(\"Note that empty commits are commented out\");\n \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n \t}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..f531e00ba7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -5,7 +5,7 @@ struct strbuf;\n struct repository;\n struct todo_list;\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(unsigned no_ask_empty, int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf);\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/sequencer.c b/sequencer.c\nindex 763ccbbc45..d2c11f34b7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n \n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,\n \tempty_commit = is_original_commit_empty(commit);\n \tif (empty_commit < 0)\n \t\treturn empty_commit;\n-\tif (!empty_commit)\n+\tif (!empty_commit || opts->ask_on_initially_empty)\n \t\treturn 0;\n \telse\n \t\treturn 1;\n@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,\n \tchar *author = NULL;\n \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n \tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res, unborn = 0, reword = 0, allow;\n+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n \n \tif (opts->no_commit) {\n \t\t/*\n@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,\n \t\tgoto leave;\n \t}\n \n-\tallow = allow_empty(r, opts, commit);\n-\tif (allow < 0) {\n-\t\tres = allow;\n-\t\tgoto leave;\n-\t} else if (allow)\n-\t\tflags |= ALLOW_EMPTY;\n-\tif (!opts->no_commit) {\n+\tdrop_commit = 0;\n+\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n+\t\tdrop_commit = 1;\n+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n+\t} else {\n+\t\tallow = allow_empty(r, opts, commit);\n+\t\tif (allow < 0) {\n+\t\t\tres = allow;\n+\t\t\tgoto leave;\n+\t\t} else if (allow) {\n+\t\t\tflags |= ALLOW_EMPTY;\n+\t\t}\n+\t}\n+\tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n \t\telse\n@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n+\t\topts->drop_redundant_commits =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n+\t\topts->ask_on_initially_empty =\n+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n \t\t\topts->reschedule_failed_exec = 1;\n \n+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n+\t\t\topts->drop_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n+\t\t\topts->keep_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n+\t\t\topts->ask_on_initially_empty = 1;\n+\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n \tif (opts->ignore_date)\n \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n+\tif (opts->drop_redundant_commits)\n+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n+\tif (opts->keep_redundant_commits)\n+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n+\tif (opts->ask_on_initially_empty)\n+\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n \tif (opts->reschedule_failed_exec)\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \n@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n \tif (opts->allow_empty_message)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.allow-empty-message\", \"true\");\n+\tif (opts->drop_redundant_commits)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n \tif (opts->keep_redundant_commits)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n+\tif (opts->ask_on_initially_empty)\n+\t\tres |= git_config_set_in_file_gently(opts_file,\n+\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n \tif (opts->signoff)\n \t\tres |= git_config_set_in_file_gently(opts_file,\n \t\t\t\t\t\"options.signoff\", \"true\");\n@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n \t\t\t\t   unsigned flags)\n {\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tis_empty = is_original_commit_empty(commit);\n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n \n \t\tstrbuf_reset(&oneline);\n \t\tpretty_print_commit(pp, commit, &oneline);\n@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tif (!to_merge) {\n \t\t\t/* non-merge commit: easy case */\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tif (!keep_empty && is_empty)\n+\t\t\tif (is_empty && ask_empty)\n \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n \t\t\t\t    oid_to_hex(&commit->object.oid),\n@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \tstruct pretty_print_context pp = {0};\n \tstruct rev_info revs;\n \tstruct commit *commit;\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n \n \twhile ((commit = get_revision(&revs))) {\n-\t\tint is_empty  = is_original_commit_empty(commit);\n+\t\tint is_empty = is_original_commit_empty(commit);\n \n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n-\t\tif (!keep_empty && is_empty)\n+\t\tif (is_empty && drop_empty)\n+\t\t\tcontinue;\n+\t\tif (is_empty && ask_empty)\n \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n \t\tstrbuf_addf(out, \"%s %s \", insn,\n \t\t\t    oid_to_hex(&commit->object.oid));\n@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \n \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n-\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n+\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n+\t\t\t\t count_commands(todo_list),\n \t\t\t\t shortrevisions, shortonto, &buf);\n \n \tres = write_message(buf.buf, buf.len, file, 0);\ndiff --git a/sequencer.h b/sequencer.h\nindex e9a0e03ea2..1c3abb661c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -39,7 +39,9 @@ struct replay_opts {\n \tint allow_rerere_auto;\n \tint allow_empty;\n \tint allow_empty_message;\n+\tint drop_redundant_commits;\n \tint keep_redundant_commits;\n+\tint ask_on_initially_empty;\n \tint verbose;\n \tint quiet;\n \tint reschedule_failed_exec;\n@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-#define TODO_LIST_KEEP_EMPTY (1U << 0)\n+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\n@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);\n  * `--onto`, we do not want to re-generate the root commits.\n  */\n #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n+#define TODO_LIST_DROP_EMPTY (1U << 7)\n+#define TODO_LIST_ASK_EMPTY (1U << 8)\n \n \n int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 325072b0a3..d23e0bf778 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -230,7 +230,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -245,7 +245,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n test_run_rebase success --rebase-merges\n \n #       m\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nnew file mode 100755\nindex 0000000000..9d52e1417f\n--- /dev/null\n+++ b/t/t3424-rebase-empty.sh\n@@ -0,0 +1,89 @@\n+#!/bin/sh\n+\n+test_description='git rebase of commits that start or become empty'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup test repository' '\n+\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n+\ttest_write_lines A B C D E F G H I J >letters &&\n+\tgit add numbers letters &&\n+\tgit commit -m A &&\n+\n+\tgit branch upstream &&\n+\tgit branch localmods &&\n+\n+\tgit checkout upstream &&\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m B &&\n+\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C &&\n+\n+\tgit checkout localmods &&\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C2 &&\n+\n+\tgit commit --allow-empty -m D &&\n+\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m \"Five letters ought to be enough for anybody\"\n+'\n+\n+test_expect_success 'rebase --merge --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n+\n+\ttest_must_fail git rebase --skip &&\n+\tgit commit --allow-empty &&\n+\tgit rebase --continue &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n+\n+test_expect_success 'rebase --interactive --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=drop upstream &&\n+\n+\ttest_write_lines C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+\n+test_done\ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex bec48e6a1f..468ebc1bef 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\ttest_must_fail git rebase --skip &&\n+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n+\tgit commit --allow-empty &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n-- \ngitgitgadget\n\n"},{"id":"388879","messageId":"478479358ff21afe6b8374f076d2ed5e139ae6c0.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 04/15] rebase: make sure to pass along the quiet flag to the sequencer","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:48Z","receivedAt":"2019-12-24T19:55:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  | 3 ++-\n t/t3400-rebase.sh | 8 +++++++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 32026a62e8..5014c9a437 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n \treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n \t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n+\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.committer_date_is_author_date =\n@@ -1502,7 +1503,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n \t\t\t   N_(\"be quiet. implies --no-stat\"),\n-\t\t\t   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),\n+\t\t\t   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\n \t\tOPT_BIT('v', \"verbose\", &options.flags,\n \t\t\tN_(\"display a diffstat of what changed upstream\"),\n \t\t\tREBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 221b35f2df..79762b989a 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase -q is quiet' '\n+test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n \tgit rebase -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n+test_expect_success 'rebase --merge -q is quiet' '\n+\tgit checkout -B quiet topic &&\n+\tgit rebase --merge -q master >output.out 2>&1 &&\n+\ttest_must_be_empty output.out\n+'\n+\n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n \t\techo \"One\" &&\n-- \ngitgitgadget\n\n"},{"id":"388881","messageId":"ee26f5a161c2e21ee9a1723bd2851f323f93cb05.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 05/15] rebase: fix handling of restrict_revision","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:49Z","receivedAt":"2019-12-24T19:55:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nrestrict_revision in the original shell script was an excluded revision\nrange.  It is also treated that way by the am-backend.  In the\nconversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\nimplement the logic to initialize $revisions in C\", 2018-08-28)), the\ninteractive-backend accidentally treated it as a positive revision\nrather than a negated one.\n\nThis was missed as there were no tests in the testsuite that tested an\ninteractive rebase with fork-point behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  |  4 ++--\n t/t3400-rebase.sh | 20 +++++++++++++++++++-\n 2 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 5014c9a437..f1de5c8186 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \n \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n \tif (opts->restrict_revision)\n-\t\targv_array_push(&make_script_args,\n-\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n+\t\targv_array_pushf(&make_script_args, \"^%s\",\n+\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n \n \tret = sequencer_make_script(the_repository, &todo_list.buf,\n \t\t\t\t    make_script_args.argc, make_script_args.argv,\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 79762b989a..71fd6396cd 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n \tgit rebase master\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n \tgit checkout -b default-base master &&\n \tgit checkout -b default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual &&\n+\tgit checkout default-base &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout default &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+\tgit checkout -B default-base master &&\n+\tgit checkout -B default topic &&\n+\tgit config branch.default.remote . &&\n+\tgit config branch.default.merge refs/heads/default-base &&\n \tgit rebase &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n-- \ngitgitgadget\n\n"},{"id":"388880","messageId":"f2c92853b47dc5f661e9b20fd390bc6d823ad6d9.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 07/15] rebase: allow more types of rebases to fast-forward","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:51Z","receivedAt":"2019-12-24T19:55:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we dis-allowed rebases using the interactive backend from\nperforming a fast-forward to short-circuit the rebase operation.  This\nmade sense for explicitly interactive rebases and some implicitly\ninteractive rebases, but certainly became overly stringent when the\nmerge backend was re-implemented via the interactive backend.\n\nJust as the am-based rebase has always had to disable the fast-forward\nbased on a variety of conditions or flags (e.g. --signoff, --whitespace,\netc.), we need to do the same but now with a few more options.  However,\ncontinuing to use REBASE_FORCE for tracking this is problematic because\nthe interactive backend used it for a different purpose.  (When\nREBASE_FORCE wasn't set, the interactive backend would not fast-forward\nthe whole series but would fast-forward individual \"pick\" commits at the\nbeginning of the todo list, and then a squash or something would cause\nit to start generating new commits.)  So, introduce a new\nallow_preemptive_ff flag contained within cmd_rebase() and use it to\ntrack whether we are going to allow a pre-emptive fast-forward that\nshort-circuits the whole rebase.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c               | 18 ++++++++++++++----\n t/t3432-rebase-fast-forward.sh |  2 ++\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f1de5c8186..7027e34567 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1493,6 +1493,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n+\tint allow_preemptive_ff = 1;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t    options.ignore_date)\n \t\toptions.flags |= REBASE_FORCE;\n \n+\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n+\t    (action != ACTION_NONE) ||\n+\t    (exec.nr > 0) ||\n+\t    options.autosquash) {\n+\t\tallow_preemptive_ff = 0;\n+\t}\n+\n \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n \t\tconst char *option = options.git_am_opts.argv[i], *p;\n \t\tif (!strcmp(option, \"--whitespace=fix\") ||\n \t\t    !strcmp(option, \"--whitespace=strip\"))\n-\t\t\toptions.flags |= REBASE_FORCE;\n+\t\t\tallow_preemptive_ff = 0;\n \t\telse if (skip_prefix(option, \"-C\", &p)) {\n \t\t\twhile (*p)\n \t\t\t\tif (!isdigit(*(p++)))\n@@ -2144,12 +2152,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t/*\n \t * Check if we are already based on onto with linear history,\n \t * in which case we could fast-forward without replacing the commits\n-\t * with new commits recreated by replaying their changes. This\n-\t * optimization must not be done if this is an interactive rebase.\n+\t * with new commits recreated by replaying their changes.\n+\t *\n+\t * Note that can_fast_forward() initializes merge_base, so we have to\n+\t * call it before checking allow_preemptive_ff.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\t    allow_preemptive_ff) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 7432c0e241..40388ccf9f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -30,6 +30,8 @@ test_rebase_same_head () {\n \tshift &&\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\n \n test_rebase_same_head_ () {\n-- \ngitgitgadget\n\n"},{"id":"388882","messageId":"34a69def330c2d0fc00f34b0897604c55bed1514.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 06/15] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:50Z","receivedAt":"2019-12-24T19:55:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3432 had several stress tests for can_fast_forward(), whose intent was\nto ensure we were using the optimization of just fast forwarding when\npossible.  However, these tests verified that fast forwards had happened\nbased on the output that rebase printed to the terminal.  We can instead\ntest more directly that we actually fast-forwarded by checking the\nreflog, which also has the side effect of making the tests applicable\nfor the merge/interactive backend.\n\nThis change does lose the distinction between \"noop\" and \"noop-force\",\nbut as stated in commit c9efc216830f (\"t3432: test for --no-ff's\ninteraction with fast-forward\", 2019-08-27) which introduced that\ndistinction: \"These tests aren't supposed to endorse the status quo,\njust test for what we're currently doing.\".\n\nThis change does not actually run these tests with the merge/interactive\nbackend; instead this is just a preparatory commit.  A subsequent commit\nwhich fixes can_fast_forward() to work with that backend will then also\nchange t3432 to add tests of that backend as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------\n 1 file changed, 22 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 92f95b57da..7432c0e241 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,19 +44,15 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tcp .git/logs/HEAD expect &&\n \t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n-\t\t\t# Must check this case first, for 'is up to\n-\t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\told=\\$(wc -l <expect) &&\n+\t\t\ttest_line_count '-gt' \\$old .git/logs/HEAD\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout &&\n-\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n-\t\telif test $what = noop-force\n-\t\tthen\n-\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n+\t\t\ttest_cmp expect .git/logs/HEAD\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -71,14 +67,14 @@ test_rebase_same_head_ () {\n \n changes='no changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '\n \n changes='our changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head success noop same success work diff --keep-base master\n test_rebase_same_head success noop same success work diff --keep-base\n-- \ngitgitgadget\n\n"},{"id":"388884","messageId":"b307340f7c79c87b2b5a5a2a25d98ef68be4ba4f.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 08/15] git-rebase.txt: add more details about behavioral differences of backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:52Z","receivedAt":"2019-12-24T19:55:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---\n t/t3433-rebase-options-compatibility.sh |   5 +-\n 2 files changed, 94 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex ff32ca1080..f1ace07c38 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used\n with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n-\tBehaves differently depending on which backend is selected.\n-+\n-'am' backend: When applying a patch, ignore changes in whitespace in\n-context lines if necessary.\n-+\n-'interactive' backend: Treat lines with only whitespace changes as\n-unchanged for the sake of a three-way merge.\n+\tIgnore whitespace-only changes in the commits being rebased,\n+\twhich may avoid \"unnecessary\" conflicts.  (Both backends\n+\tcurrently have differing edgecase bugs with this option; see\n+\tBEHAVIORAL DIFFERENCES.)\n \n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n-Directory rename heuristics are enabled in the merge and interactive\n-backends.  Due to the lack of accurate tree information, directory\n-rename detection is disabled in the am backend.\n+Due to the lack of accurate tree information (arising from\n+constructing fake ancestors with the limited information available in\n+patches), directory rename detection is disabled in the am backend.\n+Disabled directory rename detection means that if one side of history\n+renames a directory and the other adds new files to the old directory,\n+then the new files will be left behind in the old directory without\n+any warning at the time of rebasing that you may want to move these\n+files into the new directory.\n+\n+Directory rename detection works with the merge and interactive\n+backends to provide you warnings in such cases.\n+\n+Context\n+~~~~~~~\n+\n+The am backend works by creating a sequence of patches (by calling\n+`format-patch` internally), and then applying the patches in sequence\n+(calling `am` internally).  Patches are composed of multiple hunks,\n+each with line numbers, a context region, and the actual changes.  The\n+line numbers have to be taken with some fuzz, since the other side\n+will likely have inserted or deleted lines earlier in the file.  The\n+context region is meant to help find how to adjust the line numbers in\n+order to apply the changes to the right lines.  However, if multiple\n+areas of the code have the same surrounding lines of context, the\n+wrong one can be picked.  There are real-world cases where this has\n+caused commits to be reapplied incorrectly with no conflicts reported.\n+Setting diff.context to a larger value may prevent such types of\n+problems, but increases the chance of spurious conflicts (since it\n+will require more lines of matching context to apply).\n+\n+The interactive backend works with a full copy of each relevant file,\n+insulating it from these types of problems.\n+\n+Labelling of conflicts markers\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+When there are content conflicts, the merge machinery tries to\n+annotate each side's conflict markers with the commits where the\n+content came from.  Since the am backend drops the original\n+information about the rebased commits and their parents (and instead\n+generates new fake commits based off limited information in the\n+generated patches), those commits cannot be identified; instead it has\n+to fall back to a commit summary.  Also, when merge.conflictStyle is\n+set to diff3, the am backend will use \"constructed merge base\" to\n+label the content from the merge base, and thus provide no information\n+about the merge base commit whatsoever.\n+\n+The interactive backend works with the full commits on both sides of\n+history and thus has no such limitations.\n+\n+--ignore-whitespace\n+~~~~~~~~~~~~~~~~~~~\n+\n+The --ignore-whitespace option is supposed to ignore whitespace-only\n+changes if it allows the code to merge cleanly.  Unfortunately, the\n+different backends implement this differently, and both have different\n+edge case bugs.\n++\n+'am' backend: When applying a patch, ignore changes in whitespace in\n+context lines if necessary.  (Which implies that if the whitespace\n+change was not in the context lines but on a line with a real change,\n+then the rebase will still fail with \"unnecessary\" content conflicts.)\n++\n+'interactive' backend: Treat lines with only whitespace changes as\n+unchanged for the sake of a three-way merge.  This means that if one\n+side made no changes and the commits being rebased had whitespace-only\n+changes, those whitespaces fixups will be discarded despite the fact\n+that they present no content conflict.\n+\n+Miscellaneous differences\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+There are a few more behavioral differences that most folks would\n+probably consider inconsequential but which are mentioned for\n+completeness:\n+\n+* Reflog: The two backends will use different wording when describing\n+  the changes made in the reflog, though both will make use of the\n+  word \"rebase\".\n+\n+* Progress, informational, and error messages: The two backends\n+  provide slightly different progress and informational messages.\n+  Also, the am backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the interactive backend\n+  writes them to stderr.\n+\n+* State directories: The two backends keep their state in different\n+  directories under .git/\n \n include::merge-strategies.txt[]\n \ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex 5166f158dd..bd4d2d2f63 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe\n GIT_AUTHOR_DATE=\"1999-04-02T08:03:20+05:30\"\n export GIT_AUTHOR_DATE\n \n-# This is a special case in which both am and interactive backends\n-# provide the same output. It was done intentionally because\n-# both the backends fall short of optimal behaviour.\n+# This is a common case in which both am and interactive backends\n+# provide the same output with --ignore-whitespace.\n test_expect_success 'setup' '\n \tgit checkout -b topic &&\n \tq_to_tab >file <<-\\EOF &&\n-- \ngitgitgadget\n\n"},{"id":"388883","messageId":"ff43593211fe913bfab4890ec3c7a60c4c4015cb.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 11/15] git-prompt: change the prompt for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:55Z","receivedAt":"2019-12-24T19:55:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we had different prompts for different types of rebases:\n   REBASE: for am-based rebases\n   REBASE-m: for merge-based rebases\n   REBASE-i: for interactive-based rebases\n\nIt's not clear why this distinction was necessary or helpful; when the\nprompt was added in commit e75201963f67 (\"Improve bash prompt to detect\nvarious states like an unfinished merge\", 2007-09-30), it simply added\nthese three different types.  Perhaps there was a useful purpose back\nthen, but there have been some changes:\n\n  * The merge backend was deleted after being implemented on top of the\n    interactive backend, causing the prompt for merge-based rebases to\n    change from REBASE-m to REBASE-i.\n  * The interactive backend is used for multiple different types of\n    non-interactive rebases, so the \"-i\" part of the prompt doesn't\n    really mean what it used to.\n  * Rebase backends have gained more abilities and have a great deal of\n    overlap, sometimes making it hard to distinguish them.\n  * Behavioral differences between the backends have also been ironed\n    out.\n  * We want to change the default backend from am to interactive, which\n    means people would get \"REBASE-i\" by default if we didn't change\n    the prompt, and only if they specified --am or --whitespace or -C\n    would they get the \"REBASE\" prompt.\n  * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n    run the interactive backend once it can handle everything the\n    am-backend can.\n\nFor all these reasons, make the prompt for any type of rebase just be\n\"REBASE\".\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n contrib/completion/git-prompt.sh | 6 +-----\n t/t9903-bash-prompt.sh           | 8 ++++----\n 2 files changed, 5 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\nindex 1d510cd47b..014cd7c3cf 100644\n--- a/contrib/completion/git-prompt.sh\n+++ b/contrib/completion/git-prompt.sh\n@@ -429,11 +429,7 @@ __git_ps1 ()\n \t\t__git_eread \"$g/rebase-merge/head-name\" b\n \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n \t\t__git_eread \"$g/rebase-merge/end\" total\n-\t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n-\t\t\tr=\"|REBASE-i\"\n-\t\telse\n-\t\t\tr=\"|REBASE-m\"\n-\t\tfi\n+\t\tr=\"|REBASE\"\n \telse\n \t\tif [ -d \"$g/rebase-apply\" ]; then\n \t\t\t__git_eread \"$g/rebase-apply/next\" step\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 88bc733ad6..7ca35d358d 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '\n '\n \n test_expect_success 'prompt - interactive rebase' '\n-\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n+\tprintf \" (b1|REBASE 2/3)\" >expected &&\n \twrite_script fake_editor.sh <<-\\EOF &&\n \t\techo \"exec echo\" >\"$1\"\n \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '\n '\n \n test_expect_success 'prompt - rebase merge' '\n-\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n+\tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n \ttest_must_fail git rebase --merge b1 b2 &&\n@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n \ttest_cmp expected \"$actual\"\n '\n \n-test_expect_success 'prompt - rebase' '\n+test_expect_success 'prompt - rebase am' '\n \tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase b1 b2 &&\n+\ttest_must_fail git rebase --am b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n\n"},{"id":"388885","messageId":"99388f24e56c4db7a92a65633d8d7e77a11b88e3.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 12/15] rebase tests: mark tests specific to the am-backend with --am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:56Z","receivedAt":"2019-12-24T19:55:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have many rebase tests in the testsuite, and often the same test is\nrepeated multiple times just testing different backends.  For those\ntests that were specifically trying to test the am backend, add the --am\nflag.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3400-rebase.sh                       | 10 +++++-----\n t/t3401-rebase-and-am-rename.sh         |  4 ++--\n t/t3404-rebase-interactive.sh           |  2 +-\n t/t3406-rebase-message.sh               | 12 ++++++------\n t/t3407-rebase-abort.sh                 |  6 +++---\n t/t3420-rebase-autostash.sh             |  2 +-\n t/t3425-rebase-topology-merges.sh       |  8 ++++----\n t/t3432-rebase-fast-forward.sh          |  4 ++--\n t/t3433-rebase-options-compatibility.sh |  8 ++++----\n t/t5407-post-rewrite-hook.sh            | 12 ++++++------\n t/t7512-status-help.sh                  | 12 ++++++------\n 11 files changed, 40 insertions(+), 40 deletions(-)\n\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 71fd6396cd..0a491f2363 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \n test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase -q master >output.out 2>&1 &&\n+\tgit rebase --am -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex a0b9438b22..50803958fd 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase (am): directory rename detected' '\n+test_expect_failure 'rebase --am: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase A &&\n+\t\tgit -c merge.directoryRenames=true rebase --am A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ae6e55ce79..743b7e511a 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 0c2c569f95..7ce617fc1f 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase master >out &&\n+\tgit rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase master >out &&\n+\tgit rebase --force-rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase master topic >out &&\n+\tgit rebase --am master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase topic >out &&\n+\tgit rebase --am topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase reflog-onto &&\n+\tgit rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 910f218284..3e31826170 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --quit' '\n+test_expect_success 'rebase --am --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase master &&\n+\ttest_must_fail git rebase --am master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 5f7e73cf83..3816159e20 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex fd8efe84fe..19700b025b 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' ''\n+test_run_rebase success 'n o e' --am\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 40388ccf9f..4b3cecce56 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\nindex bd4d2d2f63..a07e1f276b 100755\n--- a/t/t3433-rebase-options-compatibility.sh\n+++ b/t/t3433-rebase-options-compatibility.sh\n@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '\n \tnew line 2\n \tline 3\n \tEOF\n-\ttest_must_fail git rebase main side &&\n+\ttest_must_fail git rebase --am main side &&\n \tgit rebase --abort &&\n-\tgit rebase --ignore-whitespace main side &&\n+\tgit rebase --am --ignore-whitespace main side &&\n \ttest_cmp expect file\n '\n \n@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '\n \n test_expect_success '--committer-date-is-author-date works with am backend' '\n \tgit commit --amend &&\n-\tgit rebase --committer-date-is-author-date HEAD^ &&\n+\tgit rebase --am --committer-date-is-author-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgit show HEAD --pretty=\"format:%ci\" >committertime &&\n \ttest_cmp authortime committertime\n@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '\n # sets to +0530.\n test_expect_success '--ignore-date works with am backend' '\n \tgit commit --amend --date=\"$GIT_AUTHOR_DATE\" &&\n-\tgit rebase --ignore-date HEAD^ &&\n+\tgit rebase --am --ignore-date HEAD^ &&\n \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n \tgrep \"+0000\" authortime\n '\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 7344253bfb..a8a73616e4 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase' '\n+test_expect_success 'git rebase --am' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip' '\n+test_expect_success 'git rebase --am --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip the last one' '\n+test_expect_success 'git rebase --am --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto D A &&\n+\ttest_must_fail git rebase --am --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 66d7a62797..d22b0acf2a 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase in progress before rebase --continue' '\n+test_expect_success 'status when rebase --am in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\n-- \ngitgitgadget\n\n"},{"id":"388890","messageId":"1df11f0b5105b1f602fdd723e0f74565e436faba.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 10/15] rebase: add an --am option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:54Z","receivedAt":"2019-12-24T19:55:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCurrently, this option doesn't do anything except error out if any\noptions requiring the interactive-backend are also passed.  However,\nwhen we make the default backend configurable later in this series, this\nflag will provide a way to override the config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 11 ++++++++++-\n builtin/rebase.c             | 18 +++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex f1ace07c38..cf1ac2e359 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--am:\n+\tUse git-am internally to rebase.  This option may become a\n+\tno-op in the future once the interactive backend handles\n+\teverything the am one does.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --empty={drop,keep,ask}::\n \tHow to handle commits that become empty (because they contain a\n \tsubset of already upstream changes) or start empty.  With drop\n@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.\n+\tever ignored.  Implies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThis flag is passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n+\tImplies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n+ * --am\n  * --whitespace\n  * -C\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex d2b99e9908..b7915fc0cb 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \treturn res && is_linear_history(onto, head);\n }\n \n+static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_AM;\n+\n+\treturn 0;\n+}\n+\n /* -i followed by -m is still -i */\n static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n {\n@@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n+\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n+\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n \t\t\tN_(\"use merging strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n@@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc) {\n+\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n \t\t/* all am options except -q are compatible only with --am */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-- \ngitgitgadget\n\n"},{"id":"388886","messageId":"8bec6df51af83665698ba6dae8d5b3a01fabe21f.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 14/15] rebase: make the backend configurable via config setting","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:58Z","receivedAt":"2019-12-24T19:55:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt |  8 ++++++++\n builtin/rebase.c                | 31 ++++++++++++++++++++++++-------\n 2 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex d98e32d812..e6ae30c999 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -5,6 +5,14 @@ rebase.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+rebase.backend::\n+\tDefault backend to use for rebasing.  Possible choices are\n+\t'am' or 'merge' (note that the merge backend is sometimes also\n+\trefered to as the interactive backend or the interactive\n+\tmachinery elsewhere in the docs).  Also, in the future, if the\n+\tmerge backend gains all remaining capabilities of the am\n+\tbackend, this setting may become unused.\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b7915fc0cb..d602b2da4c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -60,6 +60,7 @@ enum empty_type {\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n+\tconst char *default_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -103,6 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n+\t\t.default_backend = \"am\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)\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+\n \treturn git_default_config(var, value, data);\n }\n \n@@ -1928,9 +1934,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n+\t\tif (i >= 0) {\n+\t\t\tif (is_interactive(&options))\n+\t\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t\t      \"interactive or merge options\"));\n+\t\t\telse\n+\t\t\t\toptions.type = REBASE_AM;\n+\t\t}\n+\t}\n+\n+\tif (options.type == REBASE_UNSPECIFIED) {\n+\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"am\"))\n+\t\t\toptions.type = REBASE_AM;\n+\t\telse\n+\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n+\t\t\t    options.default_backend);\n \t}\n \n \tswitch (options.type) {\n@@ -1943,10 +1963,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n-\t\t/* the default rebase backend is `--am` */\n-\t\toptions.type = REBASE_AM;\n-\t\toptions.state_dir = apply_dir();\n-\t\tbreak;\n+\t\tBUG(\"options.type was just set above; should be unreachable.\");\n \t}\n \n \tif (options.empty == EMPTY_UNSPECIFIED) {\n-- \ngitgitgadget\n\n"},{"id":"388887","messageId":"7c3f2e07f3d388730f2a2ba48c70ad1590a91d99.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 09/15] rebase: move incompatibility checks between backend options a bit earlier","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:53Z","receivedAt":"2019-12-24T19:55:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7027e34567..d2b99e9908 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1906,6 +1906,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n+\tif (options.git_am_opts.argc) {\n+\t\t/* all am options except -q are compatible only with --am */\n+\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n+\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n+\t\t\t\tbreak;\n+\n+\t\tif (is_interactive(&options) && i >= 0)\n+\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t      \"interactive or merge options\"));\n+\t}\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_INTERACTIVE:\n@@ -1936,17 +1947,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (reschedule_failed_exec >= 0)\n \t\toptions.reschedule_failed_exec = reschedule_failed_exec;\n \n-\tif (options.git_am_opts.argc) {\n-\t\t/* all am options except -q are compatible only with --am */\n-\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n-\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-\t\t\t\tbreak;\n-\n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n-\t}\n-\n \tif (options.signoff) {\n \t\tif (options.type == REBASE_PRESERVE_MERGES)\n \t\t\tdie(\"cannot combine '--signoff' with \"\n-- \ngitgitgadget\n\n"},{"id":"388888","messageId":"044853fd612ee8cf6928bb7c4ebb1eacaa172aa3.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:59Z","receivedAt":"2019-12-24T19:55:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe am-backend drops information and thus limits what we can do:\n\n  * lack of full tree information from the original commits means we\n    cannot do directory rename detection and warn users that they might\n    want to move some of their new files that they placed in old\n    directories to prevent their becoming orphaned.[1]\n  * reduction in context from only having a few lines beyond those\n    changed means that when context lines are non-unique we can apply\n    patches incorrectly.[2]\n  * lack of access to original commits means that conflict marker\n    annotation has less information available.\n\nAlso, the merge/interactive backend have far more abilities, appear to\ncurrently have a slight performance advantage[3] and have room for more\noptimizations than the am backend[4] (and work is underway to take\nadvantage of some of those possibilities).\n\n[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt           |  2 +-\n builtin/rebase.c                       |  4 ++--\n t/t5520-pull.sh                        | 10 ++++++----\n t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n 4 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex cf1ac2e359..e819889a31 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.\n --merge::\n \tUse merging strategies to rebase.  When the recursive (default) merge\n \tstrategy is used, this allows rebase to be aware of renames on the\n-\tupstream side.\n+\tupstream side.  This is the default.\n +\n Note that a rebase merge works by replaying each commit from the working\n branch on top of the <upstream> branch.  Because of this, when a merge\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex d602b2da4c..938a8840b1 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -104,7 +104,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"am\",\t  \t\\\n+\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1945,7 +1945,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\toptions.type = REBASE_MERGE;\n+\t\t\timply_interactive(&options, \"--merge\");\n \t\telse if (!strcmp(options.default_backend, \"am\"))\n \t\t\toptions.type = REBASE_AM;\n \t\telse\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 3fff6a06fa..4f9e7f7ff6 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '\n \ttest_tick &&\n \tgit commit -m \"Create conflict\" seq.txt &&\n \ttest_must_fail git pull --rebase . seq 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success 'failed --rebase shows advice' '\n@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '\n \tgit checkout -f -b fails-to-rebase HEAD^ &&\n \ttest_commit v2-without-cr file \"2\" file2-lf &&\n \ttest_must_fail git pull --rebase . diverging 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success '--rebase fails with multiple branches' '\n@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull --rebase &&\n-\t\tfind .git/rebase-apply -name \"000*\" >patches &&\n-\t\ttest_line_count = 1 patches\n+\t\tcat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&\n+\t\tgrep -v -e \\# -e ^$ work >patches &&\n+\t\ttest_line_count = 1 patches &&\n+\t\trm -f work\n \t)\n '\n \ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex dbe8deac0d..aec45bca3b 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' \"\n \n \n test_expect_success 'check that rebase really failed' '\n-\ttest -d .git/rebase-apply\n+\tgit status >output &&\n+\tgrep currently.rebasing output\n '\n \n test_expect_success 'resolve, continue the rebase and dcommit' \"\n-- \ngitgitgadget\n"},{"id":"388889","messageId":"c2ba6317bf06a63a12de3eb97f6634e558554004.1577217299.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v3 13/15] rebase tests: repeat some tests using the merge backend instead of am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-12-24T19:54:57Z","receivedAt":"2019-12-24T19:55:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn order to ensure the merge/interactive backend gets similar coverage\nto the am one, add some tests for cases where previously only the am\nbackend was tested.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t5520-pull.sh                   | 17 +++++++++++++++--\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--\n 2 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 602d996a33..3fff6a06fa 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -277,14 +277,27 @@ test_expect_success '--rebase' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--rebase fast forward' '\n+test_expect_success '--rebase (merge) fast forward' '\n \tgit reset --hard before-rebase &&\n \tgit checkout -b ff &&\n \techo another modification >file &&\n \tgit commit -m third file &&\n \n \tgit checkout to-rebase &&\n-\tgit pull --rebase . ff &&\n+\tgit -c rebase.backend=merge pull --rebase . ff &&\n+\ttest_cmp_rev HEAD ff &&\n+\n+\t# The above only validates the result.  Did we actually bypass rebase?\n+\tgit reflog -1 >reflog.actual &&\n+\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\ttest_cmp reflog.expected reflog.fuzzy\n+'\n+\n+test_expect_success '--rebase (am) fast forward' '\n+\tgit reset --hard before-rebase &&\n+\n+\tgit -c rebase.backend=am pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 860542aad0..d383ce8130 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n-test_expect_success 'rebase describes fake ancestor base' '\n+test_expect_success 'rebase --merge describes parent of commit being picked' '\n \ttest_create_repo rebase &&\n \t(\n \t\tcd rebase &&\n@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '\n \t\ttest_commit master file &&\n \t\tgit checkout -b side HEAD^ &&\n \t\ttest_commit side file &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&\n+\t\tgrep \"||||||| parent of\" file\n+\t)\n+'\n+\n+test_expect_success 'rebase --am describes fake ancestor base' '\n+\t(\n+\t\tcd rebase &&\n+\t\tgit rebase --abort &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"389372","messageId":"404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com","threadId":"52496","inReplyTo":"1c2b77e94d63f86590ca934855066eca278f576e.1577217299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-07T14:37:44Z","receivedAt":"2020-01-07T14:37:54Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nThanks for working on this series, I think making the sequencer the \ndefault backend is a good idea. I have a few reservations about this \npath though...\n\nOn 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Extend the interactive machinery with the ability to handle the full\n> spread of options for how to handle commits that either start or become\n> empty (by \"become empty\" I mean the changes in a commit are a subset of\n> changes that exist upstream, so the net effect of applying the commit is\n> no changes).  Introduce a new command line flag for selecting the\n> desired behavior:\n>     --empty={drop,keep,ask}\n> with the definitions:\n>     drop: drop empty commits\n>     keep: keep empty commits\n>     ask:  provide the user a chance to interact and pick what to do with\n>           empty commits on a case-by-case basis\n\nI think we want to distinguish between commits that are empty before\nrebasing and those that become empty when they are rebased. --keep-empty \nexplicily only applies to commits that are already empty. Cherry-pick\nhas distinct options for those two cases. If I've explicitly created an \nempty commit then I want to keep it but I don't want to keep commits \nthat become empty because the changes they contain are already upstream.\n\nIf we want an option that keeps commits that become empty (Off hand I \ndon't know why we would though) we should consider if that option should \ndisable --cherry-mark when we create the todo list so that it keeps all \ncommits that become empty when they're rebased.\n\n> Note that traditionally, am-based rebases have always dropped commits\n> that either started or became empty, while interactive-based rebases\n> have defaulted to ask (and provided an option to keep commits that\n> started empty).  This difference made sense since users of an am-based\n> rebase just wanted to quickly batch apply a sequence of commits, while\n> users editing a todo list will likely want the chance to interact and\n> handle unusual cases on a case-by-case basis.  \n\nI don't see why it makes sense to drop an empty commit that I've made \njust because it is being rebased. I'm pretty sure the behavor of the \nam-based rebase is a function of `git am` not being able to create empty \ncommits.\n\n> However, not all rebases\n> using the interactive machinery are explicitly interactive anymore.  In\n> particular --merge was always meant to behave more like --am: just\n> rebase a batch of commits without popping up a todo list.\n> \n> If the --empty flag is not specified, pick defaults as follows:\n>     explicitly interactive: ask\n>     --exec: keep (exec is about checking existing commits, and often\n>                   used without actually changing the base.  Thus the\n>                   expectation is that the user doesn't necessarily want\n>                   anything to change; they just want to test).\n>     otherwise: drop\n\nI'm not sure I like changing the behavior based on --exec, I see what\nyou're getting at but it has the potential to be confusing. What if I\nwant to rearrange the commits without changing the base - why must I\nspecify --empty=keep there but not if I add --exec to the command line?\n\n> Also, this commit makes --keep-empty just imply --empty=keep, and hides\n> it from help so that we aren't confusing users with different ways to do\n> the same thing.  (I could have added a --drop-empty flag, but then that\n> invites users to specify both --keep-empty and --drop-empty and we have\n> to add sanity checking around that; it seems cleaner to have a single\n> multi-valued option.)  This actually fixes --keep-empty too; previously,\n> it only meant to sometimes keep empty commits, in particular commits\n> which started empty would be kept.  But it would still error out and ask\n> the user what to do with commits that became empty.  Now it keeps empty\n> commits, as instructed.\n\nIt certainly changes the behavior of --keep-empty but I'm not sure it \n\"fixes\" it. If I have some empty commits I want to keep as placeholders \nthen that's different from wanting to keep commits that become empty \nbecause their changes are upstream but --cherry-mark didn't detect them.\n\nIn summary I'm in favor of making it easier to drop commits that become \nempty but not tying that to the handling of commits that are empty \nbefore they are rebased.\n\nI'm also not happy that the deprecation of --keep-empty suddenly makes \n--no-keep-empty an error.\n\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Documentation/git-rebase.txt      | 35 ++++++------\n>  builtin/rebase.c                  | 83 +++++++++++++++++++++++++---\n>  rebase-interactive.c              |  4 +-\n>  rebase-interactive.h              |  2 +-\n>  sequencer.c                       | 74 +++++++++++++++++++------\n>  sequencer.h                       |  6 ++-\n>  t/t3421-rebase-topology-linear.sh |  4 +-\n>  t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++\n>  t/t3427-rebase-subtree.sh         | 16 +++---\n>  9 files changed, 263 insertions(+), 50 deletions(-)\n>  create mode 100755 t/t3424-rebase-empty.sh\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 1d0e2d27cc..ff32ca1080 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.\n>  \toriginal branch. The index and working tree are also left\n>  \tunchanged as a result.\n>  \n> +--empty={drop,keep,ask}::\n> +\tHow to handle commits that become empty (because they contain a\n> +\tsubset of already upstream changes) or start empty.  With drop\n> +\t(the default), commits that start or become empty are dropped.\n> +\tWith keep (implied by --exec), such commits are kept.  With ask\n> +\t(implied by --interactive), the rebase will halt when an empty\n> +\tcommit is applied allowing you to choose whether to drop it or\n> +\tcommit it.  Also with ask, if the rebase is interactive then\n> +\tcommits which start empty will be commented out in the todo\n> +\taction list (giving you a chance to uncomment).\n> ++\n> +Note that this has no effect on commits which are already upstream (as\n> +can be checked via `git log --cherry-mark ...`), which are always\n> +dropped by rebase.\n> ++\n> +See also INCOMPATIBLE OPTIONS below.\n> +\n>  --keep-empty::\n> -\tKeep the commits that do not change anything from its\n> -\tparents in the result.\n> +\tDeprecated alias for what is now known as --empty=keep.\n>  +\n>  See also INCOMPATIBLE OPTIONS below.\n>  \n> @@ -569,6 +585,7 @@ are incompatible with the following options:\n>   * --interactive\n>   * --exec\n>   * --keep-empty\n> + * --empty=\n>   * --edit-todo\n>   * --root when used in combination with --onto\n>  \n> @@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:\n>   * --preserve-merges and --ignore-whitespace\n>   * --preserve-merges and --committer-date-is-author-date\n>   * --preserve-merges and --ignore-date\n> + * --preserve-merges and --empty=\n>   * --keep-base and --onto\n>   * --keep-base and --root\n>  \n> @@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES\n>  \n>  There are some subtle differences how the backends behave.\n>  \n> -Empty commits\n> -~~~~~~~~~~~~~\n> -\n> -The am backend drops any \"empty\" commits, regardless of whether the\n> -commit started empty (had no changes relative to its parent to\n> -start with) or ended empty (all changes were already applied\n> -upstream in other commits).\n> -\n> -The interactive backend drops commits by default that\n> -started empty and halts if it hits a commit that ended up empty.\n> -The `--keep-empty` option exists for the interactive backend to allow\n> -it to keep commits that started empty.\n> -\n>  Directory rename detection\n>  ~~~~~~~~~~~~~~~~~~~~~~~~~~\n>  \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index ddf33bc9d4..6903249307 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -50,8 +50,16 @@ enum rebase_type {\n>  \tREBASE_PRESERVE_MERGES\n>  };\n>  \n> +enum empty_type {\n> +\tEMPTY_UNSPECIFIED = -1,\n> +\tEMPTY_DROP,\n> +\tEMPTY_KEEP,\n> +\tEMPTY_ASK\n> +};\n> +\n>  struct rebase_options {\n>  \tenum rebase_type type;\n> +\tenum empty_type empty;\n>  \tconst char *state_dir;\n>  \tstruct commit *upstream;\n>  \tconst char *upstream_name;\n> @@ -77,7 +85,6 @@ struct rebase_options {\n>  \tconst char *action;\n>  \tint signoff;\n>  \tint allow_rerere_autoupdate;\n> -\tint keep_empty;\n>  \tint autosquash;\n>  \tint ignore_whitespace;\n>  \tchar *gpg_sign_opt;\n> @@ -95,6 +102,7 @@ struct rebase_options {\n>  \n>  #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n>  \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n> +\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n>  \t\t.flags = REBASE_NO_QUIET, \t\t\\\n>  \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n>  \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n> @@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>  \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n>  \treplay.allow_empty = 1;\n>  \treplay.allow_empty_message = opts->allow_empty_message;\n> +\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n> +\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n> +\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n> +\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n>  \treplay.verbose = opts->flags & REBASE_VERBOSE;\n>  \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n>  \treplay.committer_date_is_author_date =\n> @@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,\n>  \n>  \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n>  \n> -\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n> +\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n> +\tflags |= (opts->empty == EMPTY_ASK &&\n> +\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n> +\t\t\tTODO_LIST_ASK_EMPTY : 0;\n>  \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n>  \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n>  \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n> @@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,\n>  \treturn ret;\n>  }\n>  \n> +static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n> +\t\t\t\tint unset)\n> +{\n> +\tstruct rebase_options *opts = opt->value;\n> +\n> +\tBUG_ON_OPT_NEG(unset);\n> +\tBUG_ON_OPT_ARG(arg);\n> +\n> +\topts->empty = EMPTY_KEEP;\n> +\topts->type = REBASE_INTERACTIVE;\n> +\treturn 0;\n> +}\n> +\n>  static const char * const builtin_rebase_interactive_usage[] = {\n>  \tN_(\"git rebase--interactive [<options>]\"),\n>  \tNULL\n> @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n>  \tstruct option options[] = {\n>  \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n>  \t\t\t   REBASE_FORCE),\n> -\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n> +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n\nIt is all very well deprecating --keep-empty but suddenly making \n'--no-keep-empty' an error goes beyond deprecation. Also I'm not sure \nit's worth changing these options as I think the only user is \ngit-rebase--preserve-merges.sh\n\nBest Wishes\n\nPhillip\n\n> +\t\t\tparse_opt_keep_empty },\n>\t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n>  \t\t\t N_(\"allow commits with empty messages\")),\n>  \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n> @@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n>  \t\topts->allow_rerere_autoupdate ?\n>  \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n>  \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n> -\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n> +\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n>  \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n>  \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n>  \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n> @@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n>  \treturn 0;\n>  }\n>  \n> +static enum empty_type parse_empty_value(const char *value)\n> +{\n> +\tif (!strcasecmp(value, \"drop\"))\n> +\t\treturn EMPTY_DROP;\n> +\telse if (!strcasecmp(value, \"keep\"))\n> +\t\treturn EMPTY_KEEP;\n> +\telse if (!strcasecmp(value, \"ask\"))\n> +\t\treturn EMPTY_ASK;\n> +\n> +\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n> +}\n> +\n> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tstruct rebase_options *options = opt->value;\n> +\tenum empty_type value = parse_empty_value(arg);\n> +\n> +\tBUG_ON_OPT_NEG(unset);\n> +\n> +\toptions->empty = value;\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> @@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\t\t\t \"ignoring them\"),\n>  \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>  \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n> -\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n> -\t\t\t N_(\"preserve empty commits during rebase\")),\n> +\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n> +\t\t\t       N_(\"how to handle empty commits\"),\n> +\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n> +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n> +\t\t\tparse_opt_keep_empty },\n>  \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n>  \t\t\t N_(\"move commits that begin with \"\n>  \t\t\t    \"squash!/fixup! under -i\")),\n> @@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \tif (!(options.flags & REBASE_NO_QUIET))\n>  \t\targv_array_push(&options.git_am_opts, \"-q\");\n>  \n> -\tif (options.keep_empty)\n> -\t\timply_interactive(&options, \"--keep-empty\");\n> +\tif (options.empty != EMPTY_UNSPECIFIED)\n> +\t\timply_interactive(&options, \"--empty\");\n>  \n>  \tif (gpg_sign) {\n>  \t\tfree(options.gpg_sign_opt);\n> @@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tbreak;\n>  \t}\n>  \n> +\tif (options.empty == EMPTY_UNSPECIFIED) {\n> +\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n> +\t\t\toptions.empty = EMPTY_ASK;\n> +\t\telse if (exec.nr > 0)\n> +\t\t\toptions.empty = EMPTY_KEEP;\n> +\t\telse\n> +\t\t\toptions.empty = EMPTY_DROP;\n> +\t}\n>  \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n>  \t\tdie(_(\"--reschedule-failed-exec requires \"\n>  \t\t      \"--exec or --interactive\"));\n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index aa18ae82b7..ad82bf77df 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n>  \treturn MISSING_COMMIT_CHECK_IGNORE;\n>  }\n>  \n> -void append_todo_help(unsigned keep_empty, int command_count,\n> +void append_todo_help(unsigned no_ask_empty, int command_count,\n>  \t\t      const char *shortrevisions, const char *shortonto,\n>  \t\t      struct strbuf *buf)\n>  {\n> @@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,\n>  \n>  \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n>  \n> -\tif (!keep_empty) {\n> +\tif (!no_ask_empty) {\n>  \t\tmsg = _(\"Note that empty commits are commented out\");\n>  \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n>  \t}\n> diff --git a/rebase-interactive.h b/rebase-interactive.h\n> index 44dbb06311..f531e00ba7 100644\n> --- a/rebase-interactive.h\n> +++ b/rebase-interactive.h\n> @@ -5,7 +5,7 @@ struct strbuf;\n>  struct repository;\n>  struct todo_list;\n>  \n> -void append_todo_help(unsigned keep_empty, int command_count,\n> +void append_todo_help(unsigned no_ask_empty, int command_count,\n>  \t\t      const char *shortrevisions, const char *shortonto,\n>  \t\t      struct strbuf *buf);\n>  int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n> diff --git a/sequencer.c b/sequencer.c\n> index 763ccbbc45..d2c11f34b7 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n>  static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n>  static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n>  static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n> +static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n>  \n>  static int git_sequencer_config(const char *k, const char *v, void *cb)\n>  {\n> @@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,\n>  \tempty_commit = is_original_commit_empty(commit);\n>  \tif (empty_commit < 0)\n>  \t\treturn empty_commit;\n> -\tif (!empty_commit)\n> +\tif (!empty_commit || opts->ask_on_initially_empty)\n>  \t\treturn 0;\n>  \telse\n>  \t\treturn 1;\n> @@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,\n>  \tchar *author = NULL;\n>  \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n>  \tstruct strbuf msgbuf = STRBUF_INIT;\n> -\tint res, unborn = 0, reword = 0, allow;\n> +\tint res, unborn = 0, reword = 0, allow, drop_commit;\n>  \n>  \tif (opts->no_commit) {\n>  \t\t/*\n> @@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,\n>  \t\tgoto leave;\n>  \t}\n>  \n> -\tallow = allow_empty(r, opts, commit);\n> -\tif (allow < 0) {\n> -\t\tres = allow;\n> -\t\tgoto leave;\n> -\t} else if (allow)\n> -\t\tflags |= ALLOW_EMPTY;\n> -\tif (!opts->no_commit) {\n> +\tdrop_commit = 0;\n> +\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n> +\t\tdrop_commit = 1;\n> +\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n> +\t} else {\n> +\t\tallow = allow_empty(r, opts, commit);\n> +\t\tif (allow < 0) {\n> +\t\t\tres = allow;\n> +\t\t\tgoto leave;\n> +\t\t} else if (allow) {\n> +\t\t\tflags |= ALLOW_EMPTY;\n> +\t\t}\n> +\t}\n> +\tif (!opts->no_commit && !drop_commit) {\n>  \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>  \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n>  \t\telse\n> @@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)\n\nThis function is used by cherry-pick/revert not rebase do we need to\nchange it?\n\n>  \telse if (!strcmp(key, \"options.allow-empty-message\"))\n>  \t\topts->allow_empty_message =\n>  \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n> +\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n> +\t\topts->drop_redundant_commits =\n> +\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n>  \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n>  \t\topts->keep_redundant_commits =\n>  \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n> +\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n> +\t\topts->ask_on_initially_empty =\n> +\t\t\tgit_config_bool_or_int(key, value, &error_flag);>  \telse if (!strcmp(key, \"options.signoff\"))\n>  \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n>  \telse if (!strcmp(key, \"options.record-origin\"))\n> @@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)\n>  \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n>  \t\t\topts->reschedule_failed_exec = 1;\n>  \n> +\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n> +\t\t\topts->drop_redundant_commits = 1;\n> +\n> +\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n> +\t\t\topts->keep_redundant_commits = 1;\n> +\n> +\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n> +\t\t\topts->ask_on_initially_empty = 1;\n> +\n>  \t\tread_strategy_opts(opts, &buf);\n>  \t\tstrbuf_release(&buf);\n>  \n> @@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n>  \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n>  \tif (opts->ignore_date)\n>  \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n> +\tif (opts->drop_redundant_commits)\n> +\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n> +\tif (opts->keep_redundant_commits)\n> +\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n> +\tif (opts->ask_on_initially_empty)\n> +\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n>  \tif (opts->reschedule_failed_exec)\n>  \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n>  \n> @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n\nagain this is for cherry-pick/revert\n\n>  \tif (opts->allow_empty_message)\n>  \t\tres |= git_config_set_in_file_gently(opts_file,\n>  \t\t\t\t\"options.allow-empty-message\", \"true\");\n> +\tif (opts->drop_redundant_commits)\n> +\t\tres |= git_config_set_in_file_gently(opts_file,\n> +\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n>  \tif (opts->keep_redundant_commits)\n>  \t\tres |= git_config_set_in_file_gently(opts_file,\n>  \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n> +\tif (opts->ask_on_initially_empty)\n> +\t\tres |= git_config_set_in_file_gently(opts_file,\n> +\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n>  \tif (opts->signoff)\n>  \t\tres |= git_config_set_in_file_gently(opts_file,\n>  \t\t\t\t\t\"options.signoff\", \"true\");\n> @@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n>  \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n>  \t\t\t\t   unsigned flags)\n>  {\n> -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> +\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n> +\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n>  \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n>  \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n>  \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n> @@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n>  \t\tis_empty = is_original_commit_empty(commit);\n>  \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n>  \t\t\tcontinue;\n> +\t\tif (is_empty && drop_empty)\n> +\t\t\tcontinue;\n>  \n>  \t\tstrbuf_reset(&oneline);\n>  \t\tpretty_print_commit(pp, commit, &oneline);\n> @@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n>  \t\tif (!to_merge) {\n>  \t\t\t/* non-merge commit: easy case */\n>  \t\t\tstrbuf_reset(&buf);\n> -\t\t\tif (!keep_empty && is_empty)\n> +\t\t\tif (is_empty && ask_empty)\n>  \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n>  \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n>  \t\t\t\t    oid_to_hex(&commit->object.oid),\n> @@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n>  \tstruct pretty_print_context pp = {0};\n>  \tstruct rev_info revs;\n>  \tstruct commit *commit;\n> -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> +\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n> +\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n>  \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n>  \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n>  \n> @@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n>  \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n>  \n>  \twhile ((commit = get_revision(&revs))) {\n> -\t\tint is_empty  = is_original_commit_empty(commit);\n> +\t\tint is_empty = is_original_commit_empty(commit);\n>  \n>  \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n>  \t\t\tcontinue;\n> -\t\tif (!keep_empty && is_empty)\n> +\t\tif (is_empty && drop_empty)\n> +\t\t\tcontinue;\n> +\t\tif (is_empty && ask_empty)\n>  \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n>  \t\tstrbuf_addf(out, \"%s %s \", insn,\n>  \t\t\t    oid_to_hex(&commit->object.oid));\n> @@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n>  \n>  \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n>  \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n> -\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n> +\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n> +\t\t\t\t count_commands(todo_list),\n>  \t\t\t\t shortrevisions, shortonto, &buf);\n>  \n>  \tres = write_message(buf.buf, buf.len, file, 0);\n> diff --git a/sequencer.h b/sequencer.h\n> index e9a0e03ea2..1c3abb661c 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -39,7 +39,9 @@ struct replay_opts {\n>  \tint allow_rerere_auto;\n>  \tint allow_empty;\n>  \tint allow_empty_message;\n> +\tint drop_redundant_commits;\n>  \tint keep_redundant_commits;\n> +\tint ask_on_initially_empty;\n>  \tint verbose;\n>  \tint quiet;\n>  \tint reschedule_failed_exec;\n> @@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n>  int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n>  int sequencer_remove_state(struct replay_opts *opts);\n>  \n> -#define TODO_LIST_KEEP_EMPTY (1U << 0)\n> +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n>  #define TODO_LIST_SHORTEN_IDS (1U << 1)\n>  #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n>  #define TODO_LIST_REBASE_MERGES (1U << 3)\n> @@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);\n>   * `--onto`, we do not want to re-generate the root commits.\n>   */\n>  #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n> +#define TODO_LIST_DROP_EMPTY (1U << 7)\n> +#define TODO_LIST_ASK_EMPTY (1U << 8)\n>  \n>  \n>  int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n> diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n> index 325072b0a3..d23e0bf778 100755\n> --- a/t/t3421-rebase-topology-linear.sh\n> +++ b/t/t3421-rebase-topology-linear.sh\n> @@ -230,7 +230,7 @@ test_run_rebase () {\n>  test_run_rebase success ''\n>  test_run_rebase success -m\n>  test_run_rebase success -i\n> -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> +test_have_prereq !REBASE_P || test_run_rebase success -p\n>  \n>  test_run_rebase () {\n>  \tresult=$1\n> @@ -245,7 +245,7 @@ test_run_rebase () {\n>  test_run_rebase success ''\n>  test_run_rebase success -m\n>  test_run_rebase success -i\n> -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> +test_have_prereq !REBASE_P || test_run_rebase success -p\n>  test_run_rebase success --rebase-merges\n>  \n>  #       m\n> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n> new file mode 100755\n> index 0000000000..9d52e1417f\n> --- /dev/null\n> +++ b/t/t3424-rebase-empty.sh\n> @@ -0,0 +1,89 @@\n> +#!/bin/sh\n> +\n> +test_description='git rebase of commits that start or become empty'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup test repository' '\n> +\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n> +\ttest_write_lines A B C D E F G H I J >letters &&\n> +\tgit add numbers letters &&\n> +\tgit commit -m A &&\n> +\n> +\tgit branch upstream &&\n> +\tgit branch localmods &&\n> +\n> +\tgit checkout upstream &&\n> +\ttest_write_lines A B C D E >letters &&\n> +\tgit add letters &&\n> +\tgit commit -m B &&\n> +\n> +\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n> +\tgit add numbers &&\n> +\tgit commit -m C &&\n> +\n> +\tgit checkout localmods &&\n> +\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n> +\tgit add numbers &&\n> +\tgit commit -m C2 &&\n> +\n> +\tgit commit --allow-empty -m D &&\n> +\n> +\ttest_write_lines A B C D E >letters &&\n> +\tgit add letters &&\n> +\tgit commit -m \"Five letters ought to be enough for anybody\"\n> +'\n> +\n> +test_expect_success 'rebase --merge --empty=drop' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --merge --empty=drop upstream &&\n> +\n> +\ttest_write_lines C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --merge --empty=keep' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --merge --empty=keep upstream &&\n> +\n> +\ttest_write_lines D C2 C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --merge --empty=ask' '\n> +\tgit checkout -B testing localmods &&\n> +\ttest_must_fail git rebase --merge --empty=ask upstream &&\n> +\n> +\ttest_must_fail git rebase --skip &&\n> +\tgit commit --allow-empty &&\n> +\tgit rebase --continue &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n> +\n> +test_expect_success 'rebase --interactive --empty=drop' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --interactive --empty=drop upstream &&\n> +\n> +\ttest_write_lines C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --interactive --empty=keep' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --interactive --empty=keep upstream &&\n> +\n> +\ttest_write_lines D C2 C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +\n> +test_done\n> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n> index bec48e6a1f..468ebc1bef 100755\n> --- a/t/t3427-rebase-subtree.sh\n> +++ b/t/t3427-rebase-subtree.sh\n> @@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n>  \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>  '\n>  \n> -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n>  \treset_rebase &&\n>  \tgit checkout -b rebase-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n>  \t: first pick results in no changes &&\n> -\tgit rebase --continue &&\n> +\ttest_must_fail git rebase --skip &&\n> +\t: last pick was an empty commit that has no changes, but we want to keep it &&\n> +\tgit commit --allow-empty &&\n>  \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>  \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>  \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>  '\n>  \n> -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n>  \treset_rebase &&\n>  \tgit checkout -b rebase-merges-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n>  \t: first pick results in no changes &&\n> -\tgit rebase --continue &&\n> +\ttest_must_fail git rebase --skip &&\n> +\t: last pick was an empty commit that has no changes, but we want to keep it &&\n> +\tgit commit --allow-empty &&\n>  \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>  \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>  \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n> \n\n"},{"id":"389373","messageId":"8f2fa083-114a-011f-1480-ae0ebd67d814@gmail.com","threadId":"52496","inReplyTo":"1df11f0b5105b1f602fdd723e0f74565e436faba.1577217299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-07T14:43:33Z","receivedAt":"2020-01-07T14:43:38Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Currently, this option doesn't do anything except error out if any\n> options requiring the interactive-backend are also passed.  However,\n> when we make the default backend configurable later in this series, this\n> flag will provide a way to override the config setting.\n\nI wonder if we really want to add a new option that we know we don't \nwant to keep in the long term. Could we not just default to the \ninteractive backend and fallback to the am backend if the user passes \nany of the am-based options on the commandline? i.e. reverse the current \nsituation of defaulting to the am backend and selecting the interactive \nbackend when the user passes certain options.\n\nBest Wishes\n\nPhillip\n\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>   Documentation/git-rebase.txt | 11 ++++++++++-\n>   builtin/rebase.c             | 18 +++++++++++++++++-\n>   2 files changed, 27 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index f1ace07c38..cf1ac2e359 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n>   \toriginal branch. The index and working tree are also left\n>   \tunchanged as a result.\n>   \n> +--am:\n> +\tUse git-am internally to rebase.  This option may become a\n> +\tno-op in the future once the interactive backend handles\n> +\teverything the am one does.\n> ++\n> +See also INCOMPATIBLE OPTIONS below.\n> +\n>   --empty={drop,keep,ask}::\n>   \tHow to handle commits that become empty (because they contain a\n>   \tsubset of already upstream changes) or start empty.  With drop\n> @@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.\n>   \tEnsure at least <n> lines of surrounding context match before\n>   \tand after each change.  When fewer lines of surrounding\n>   \tcontext exist they all must match.  By default no context is\n> -\tever ignored.\n> +\tever ignored.  Implies --am.\n>   +\n>   See also INCOMPATIBLE OPTIONS below.\n>   \n> @@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.\n>   --whitespace=<option>::\n>   \tThis flag is passed to the 'git apply' program\n>   \t(see linkgit:git-apply[1]) that applies the patch.\n> +\tImplies --am.\n>   +\n>   See also INCOMPATIBLE OPTIONS below.\n>   \n> @@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS\n>   \n>   The following options:\n>   \n> + * --am\n>    * --whitespace\n>    * -C\n>   \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index d2b99e9908..b7915fc0cb 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n>   \treturn res && is_linear_history(onto, head);\n>   }\n>   \n> +static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tstruct rebase_options *opts = opt->value;\n> +\n> +\tBUG_ON_OPT_NEG(unset);\n> +\tBUG_ON_OPT_ARG(arg);\n> +\n> +\topts->type = REBASE_AM;\n> +\n> +\treturn 0;\n> +}\n> +\n>   /* -i followed by -m is still -i */\n>   static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n>   {\n> @@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n>   \t\t\t    N_(\"show the patch file being applied or merged\"),\n>   \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n> +\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n> +\t\t\tN_(\"use apply-mail strategies to rebase\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n> +\t\t\tparse_opt_am },\n>   \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n>   \t\t\tN_(\"use merging strategies to rebase\"),\n>   \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n> @@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n>   \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n>   \n> -\tif (options.git_am_opts.argc) {\n> +\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n>   \t\t/* all am options except -q are compatible only with --am */\n>   \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n>   \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n> \n"},{"id":"389414","messageId":"CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com","threadId":"52496","inReplyTo":"404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com","subject":"Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-07T19:15:08Z","receivedAt":"2020-01-07T19:15:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Phillip,\n\nOn Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n> Thanks for working on this series, I think making the sequencer the\n> default backend is a good idea. I have a few reservations about this\n> path though...\n\nThanks for the feedback.  You've provided some good food for thought.\nWhile responding, I came up with an alternate proposal below which I\nthink may be more in line with what you want.  Let me know...\n\n>\n> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Extend the interactive machinery with the ability to handle the full\n> > spread of options for how to handle commits that either start or become\n> > empty (by \"become empty\" I mean the changes in a commit are a subset of\n> > changes that exist upstream, so the net effect of applying the commit is\n> > no changes).  Introduce a new command line flag for selecting the\n> > desired behavior:\n> >     --empty={drop,keep,ask}\n> > with the definitions:\n> >     drop: drop empty commits\n> >     keep: keep empty commits\n> >     ask:  provide the user a chance to interact and pick what to do with\n> >           empty commits on a case-by-case basis\n>\n> I think we want to distinguish between commits that are empty before\n> rebasing and those that become empty when they are rebased. --keep-empty\n> explicily only applies to commits that are already empty. Cherry-pick\n\nI am open to changing how the empty handling works; I figured it was\nthe most likely area people might have feedback about.\n\nHowever, I do strongly disagree with the \"explicit\" claim here.  It is\npossible that --keep-empty was designed to only apply to commits that\nbegin empty, but that is not at all explicit.  The only place in the\ndocumentation I could find that mentions the distinction at all was in\nthe \"Behavioral Differences\" section, which was written by me as part\nof commit  0661e49aeb84 (\"git-rebase.txt: document behavioral\ndifferences between modes\", 2018-06-27).  Quoting from that commit\nmessage:\n\n\"(In fact, it's not clear -- to me at least -- that these differences\nwere even desirable or intentional.)  Document these differences.\"\n\nI cannot find anywhere else making a distinction in either the\ndocumentation or the commit messages or even any code comments, which\nhas left me wondering whether the distinction was intentional at all.\nThings like commit b00bf1c9a8dd (\"git-rebase: make\n--allow-empty-message the default\", 2018-06-27) give heavy precedence\nto the assumption that git-rebase is often more happenstance than\ndesign when it comes to edge cases like these.\n\n> has distinct options for those two cases. If I've explicitly created an\n> empty commit then I want to keep it but I don't want to keep commits\n> that become empty because the changes they contain are already upstream.\n>\n> If we want an option that keeps commits that become empty (Off hand I\n> don't know why we would though) we should consider if that option should\n> disable --cherry-mark when we create the todo list so that it keeps all\n> commits that become empty when they're rebased.\n\nTo answer the particular reason I have seen why folks might want to\nkeep both commits that start empty and become empty, but still use\n--cherry-mark to discard commits that are already upstream:\n  * If cherry-mark can determine the commit was \"already upstream\",\nthen because of how cherry-mark works this means the upstream commit\nmessage was about the *exact* same set of changes.  Thus, the commit\nmessages can be assumed to be fully interchangeable (and are in fact\nlikely to be completely identical).\n  * If a commit does not start empty, but becomes so because its\nchanges were a subset of those upstream, then while there are clearly\nno more changes that need to be applied, the commit messages can be\nexpected to differ -- either there will be an upstream commit that\nincluded a bunch of other changes too, or the commit we are rebasing\nwill be spread across multiple upstream commits.  If the project has\nbad commit message hygiene (because e.g. they are using atrocious code\nreview tools like GitHub that don't allow you to review them), there's\na reasonable likelihood that the upstream version only talks about the\nother changes in the commit instead of the fix we are rebasing.  If\nso, there may be useful information in this commit message that we\nwant to copy somewhere.\n\nIt's a somewhat rare usecase involving projects that generally aren't\ncareful with commit messages but which has some people working on it\nwho are careful.  But even for these projects, the odds that a commit\nmessage will just get tossed anyway seem fairly high, so I will not\nstrongly defend this choice; it just seemed like \"--keep-empty\" or\n\"--empty=keep\" should mean \"keep empty\", not \"sometimes keep empty\".\nSo, if it'd be nice if we could come up with clear wording if we are\ngoing to implement other possibilities.\n\nAnyway, am I correctly understanding that you would like to see the\nfollowing matrix of options?\n  * Drop commits that either start or become empty [the current --empty=drop]\n  * Pause and ask users whether they want commits that either start or\nbecome empty [the current --empty=ask]\n  * Keep commits that start empty, but drop ones that become empty [a\nnew option]\nand possibly drop the current --empty=keep behavior?\n\n> > Note that traditionally, am-based rebases have always dropped commits\n> > that either started or became empty, while interactive-based rebases\n> > have defaulted to ask (and provided an option to keep commits that\n> > started empty).  This difference made sense since users of an am-based\n> > rebase just wanted to quickly batch apply a sequence of commits, while\n> > users editing a todo list will likely want the chance to interact and\n> > handle unusual cases on a case-by-case basis.\n>\n> I don't see why it makes sense to drop an empty commit that I've made\n> just because it is being rebased.\n\nAre you arguing that keeping commits that started empty should\nactually be the default, i.e. that all three rebase backends have\ntraditionally had the wrong default?\n\n> I'm pretty sure the behavor of the\n> am-based rebase is a function of `git am` not being able to create empty\n> commits.\n\nThat may be, but it's not quite clear.  Junio kind of commented on\nthis in https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/.\nHis comments that \"lack of --keep-empty on the 'am' side is probably a\nbug that wants to be fixed\" might imply that he only sees it as a\nuseful option.  If the behavior of the am-based rebase dropping\ncommits which started empty was solely a function of git-am not being\nable to create empty commits, then the correct fix would be to make\nkeeping the commits which started empty as the default or maybe only\nbehavior rather than just making it an option.\n\nI'd actually be open to changing the default here.  We have a\nprecedent in doing so with commit b00bf1c9a8dd (\"git-rebase: make\n--allow-empty-message the default\", 2018-06-27) where we stated that\nreally rare edge cases (which I think both empty commit messages and\ncommits which start empty qualify as) probably have the wrong default\nin rebase.  And changing the default would yield a much easier to\nunderstand matrix of options:\n\n  * Drop commits that become empty (modification to --empty=drop in\nthat it keeps commits which started empty)\n  * Pause and ask users if commits become empty (modification to\n--empty=ask in that it keeps commits which started empty)\n  * Keep commits that become empty, in addition to those that started\nempty (--empty=keep).\n\n> > However, not all rebases\n> > using the interactive machinery are explicitly interactive anymore.  In\n> > particular --merge was always meant to behave more like --am: just\n> > rebase a batch of commits without popping up a todo list.\n> >\n> > If the --empty flag is not specified, pick defaults as follows:\n> >     explicitly interactive: ask\n> >     --exec: keep (exec is about checking existing commits, and often\n> >                   used without actually changing the base.  Thus the\n> >                   expectation is that the user doesn't necessarily want\n> >                   anything to change; they just want to test).\n> >     otherwise: drop\n>\n> I'm not sure I like changing the behavior based on --exec, I see what\n> you're getting at but it has the potential to be confusing. What if I\n> want to rearrange the commits without changing the base - why must I\n> specify --empty=keep there but not if I add --exec to the command line?\n\nThis was probably poorly explained and also poorly implemented.  Let\nme try from a slightly different angle, and come to a slightly\ndifferent result than I did previously:\n\nQuoting from Junio in b00bf1c9a8dd (\"git-rebase: make\n--allow-empty-message the default\", 2018-06-27),\n   '\"am\" based rebase is solely to transplant an existing history and\nwant to stop much less than \"interactive\" one whose purpose is to\npolish a series before making it publishable, and asking for\nconfirmation (\"this has become empty--do you want to drop it?\") is\nmore appropriate from the workflow point of view'\n\nI would modify Junio's wording to expand \"am based rebase\" to say that\nif the user hasn't requested an interactive rebase or otherwise\nexplicitly signalled that they want some measure of interactivity\n(e.g. specified --empty=ask), then we should not stop and ask how to\nhandle commits which started OR became empty.  In other worse, it's\nnot just the am-based rebase that is about transplanting commits but\nany rebase where interactivity isn't requested.  I think the\ntraditional --keep-empty is quite broken in this regard since it does\nstop and ask with commits that become empty.  rebase -m, rebase\n--exec, etc. should not stop when a commit becomes empty, there should\nbe some default it just uses unless the user also adds\n--interactive/-i/--empty=ask.\n\nBut, if we take your above suggestion to make keeping the commits\nwhich started empty not only the default but remove it from the\noptions, then this becomes much easier.  The choices become (kind of\nrepeated from above):\n\n[Keep the commits that started empty in all three cases, otherwise:]\n  * Drop commits that become empty (--empty=drop)\n  * Pause and ask users if commits become empty (--empty=ask)\n  * Keep commits that become empty (--empty=keep)\n\nand the defaults are simply:\n   --interactive/-i: --empty=ask\n   everything else: --empty=drop\n\n(And as a bonus, in the thread I've referenced twice above Junio\nmentioned -- in respect to commits that become empty -- that he thinks\nthese are the right defaults.  He didn't comment on the behavior of\ncommits that start empty, which seems to be your primary concern.)\n\n> > Also, this commit makes --keep-empty just imply --empty=keep, and hides\n> > it from help so that we aren't confusing users with different ways to do\n> > the same thing.  (I could have added a --drop-empty flag, but then that\n> > invites users to specify both --keep-empty and --drop-empty and we have\n> > to add sanity checking around that; it seems cleaner to have a single\n> > multi-valued option.)  This actually fixes --keep-empty too; previously,\n> > it only meant to sometimes keep empty commits, in particular commits\n> > which started empty would be kept.  But it would still error out and ask\n> > the user what to do with commits that became empty.  Now it keeps empty\n> > commits, as instructed.\n>\n> It certainly changes the behavior of --keep-empty but I'm not sure it\n> \"fixes\" it. If I have some empty commits I want to keep as placeholders\n> then that's different from wanting to keep commits that become empty\n> because their changes are upstream but --cherry-mark didn't detect them.\n>\n> In summary I'm in favor of making it easier to drop commits that become\n> empty but not tying that to the handling of commits that are empty\n> before they are rebased.\n\nSounds like my modified proposal above may be in line with your tastes?\n\n> I'm also not happy that the deprecation of --keep-empty suddenly makes\n> --no-keep-empty an error.\n\nWe could translate that to --empty=drop; does that sound reasonable?\n\n[...]\n> > @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n> >       struct option options[] = {\n> >               OPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n> >                          REBASE_FORCE),\n> > -             OPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n> > +             { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> > +                     N_(\"(DEPRECATED) keep empty commits\"),\n> > +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n>\n> It is all very well deprecating --keep-empty but suddenly making\n> '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure\n> it's worth changing these options as I think the only user is\n> git-rebase--preserve-merges.sh\n\nSide track: Since git-rebase--preserve-merges.sh is deprecated and we\nwant to get rid of it, and rebase-merges exists and is a better\nimplementation of the original idea, can we just translate rebase -p\ninto rebase -r and delete git-rebase--preserve-merges.sh?  (With a few\nwrinkles, such as telling users in the middle of an existing\npreserve-merges-rebase that they either need to use an old version of\ngit to continue their rebase or else abort the rebase?)\n\n> Best Wishes\n>\n> Phillip\n>\n[...]\n> This function is used by cherry-pick/revert not rebase do we need to\n> change it?\n\nPossibly not; I can take a look.  I had quite a difficult time trying\nto figure out how to make sure that --empty options were saved and\nrestored across rebase --continue; it's not at all unlikely that I\nmodified more than I needed to \"for consistency\".\n\n[...]\n> > @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n>\n> again this is for cherry-pick/revert\n\nOkay, will double check this too.\n\n[...]\n"},{"id":"389416","messageId":"CABPp-BEoTb6LVXThEM4zoKxVOnzBNs7y-Mk+oFbb6BUzCo3RHg@mail.gmail.com","threadId":"52496","inReplyTo":"8f2fa083-114a-011f-1480-ae0ebd67d814@gmail.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-07T19:26:58Z","receivedAt":"2020-01-07T19:27:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Phillip,\n\nOn Tue, Jan 7, 2020 at 6:43 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Currently, this option doesn't do anything except error out if any\n> > options requiring the interactive-backend are also passed.  However,\n> > when we make the default backend configurable later in this series, this\n> > flag will provide a way to override the config setting.\n>\n> I wonder if we really want to add a new option that we know we don't\n> want to keep in the long term. Could we not just default to the\n> interactive backend and fallback to the am backend if the user passes\n> any of the am-based options on the commandline? i.e. reverse the current\n> situation of defaulting to the am backend and selecting the interactive\n> backend when the user passes certain options.\n\nGood question.  For context, this option served a few purposes:\n  * Providing an escape hatch during the transition in case the merge\nbackend doesn't handle everything the am one does in our first\nattempts (i.e. similar to how we allowed using the scripted rebase\neven after providing a built-in one).\n  * Makes the documentation much more concise and clear (being able to\nspecify that various flags \"imply --am\" is simple, whereas removing\nthe flag makes me feel I need the full description of --am in each of\nthe few places a flag that would have implied it).\n  * Provides an easy way to make the plethora of am-specific rebase\ntests use the am-backend.\n\nHowever, I thought of this option before Junio suggested a\nrebase.backend config setting, so we could just rely on that instead.\nThus, getting rid of the \"--am\" flag in detail would mean:\n  * I need to redo the test changes in this series to use \"-c\nrebase.backend=am\" instead of \"--am\"\n  * It will be slightly harder for users to use the escape hatch in\none-off cases during the transition\n  * We need to figure out the right way to reword the documentation\n\nThe first two are pretty minor, so that probably means I just need to\ncome up with some good wording for the documentation (suggestions\nwelcome...)\n\n\nElijah\n"},{"id":"389420","messageId":"xmqqy2uj3u3q.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BEoTb6LVXThEM4zoKxVOnzBNs7y-Mk+oFbb6BUzCo3RHg@mail.gmail.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-01-07T20:11:05Z","receivedAt":"2020-01-07T20:11:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> However, I thought of this option before Junio suggested a\n> rebase.backend config setting, so we could just rely on that instead.\n> Thus, getting rid of the \"--am\" flag in detail would mean:\n>   * I need to redo the test changes in this series to use \"-c\n> rebase.backend=am\" instead of \"--am\"\n>   * It will be slightly harder for users to use the escape hatch in\n> one-off cases during the transition\n>   * We need to figure out the right way to reword the documentation\n>\n> The first two are pretty minor, so that probably means I just need to\n> come up with some good wording for the documentation (suggestions\n> welcome...)\n\nIt probably is a good idea to keep --am (and possibly --[no-]am) as\nlong as rebase.backend option exists.  A configuration variable that\nchanges behaviour without allowing it to be overridden from the\ncommand line is not a good idea.\n"},{"id":"389477","messageId":"d9d3d360-fc45-a716-4b35-4367c017460e@gmail.com","threadId":"52496","inReplyTo":"CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com","subject":"Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-08T14:27:03Z","receivedAt":"2020-01-08T14:27:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nThanks for such a detailed reply, I'll try and do it justice.\n\nTLDR I like your new proposal\n\nOn 07/01/2020 19:15, Elijah Newren wrote:\n> Hi Phillip,\n> \n> On Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>>\n>> Hi Elijah\n>>\n>> Thanks for working on this series, I think making the sequencer the\n>> default backend is a good idea. I have a few reservations about this\n>> path though...\n> \n> Thanks for the feedback.  You've provided some good food for thought.\n> While responding, I came up with an alternate proposal below which I\n> think may be more in line with what you want.  Let me know...\n> \n>>\n>> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n>>> From: Elijah Newren <newren@gmail.com>\n>>>\n>>> Extend the interactive machinery with the ability to handle the full\n>>> spread of options for how to handle commits that either start or become\n>>> empty (by \"become empty\" I mean the changes in a commit are a subset of\n>>> changes that exist upstream, so the net effect of applying the commit is\n>>> no changes).  Introduce a new command line flag for selecting the\n>>> desired behavior:\n>>>     --empty={drop,keep,ask}\n>>> with the definitions:\n>>>     drop: drop empty commits\n>>>     keep: keep empty commits\n>>>     ask:  provide the user a chance to interact and pick what to do with\n>>>           empty commits on a case-by-case basis\n>>\n>> I think we want to distinguish between commits that are empty before\n>> rebasing and those that become empty when they are rebased. --keep-empty\n>> explicily only applies to commits that are already empty. Cherry-pick\n> \n> I am open to changing how the empty handling works; I figured it was\n> the most likely area people might have feedback about.\n> \n> However, I do strongly disagree with the \"explicit\" claim here.  It is\n> possible that --keep-empty was designed to only apply to commits that\n> begin empty, but that is not at all explicit.\n\nMaybe explicit wasn't the best choice of words. I was going off the\nimplementation which takes pains to only allow and empty commit when the\noriginal was empty. This has been the case since the feature was\nintroduced in 90e1818f9a (\"git-rebase: add keep_empty flag\",\n2012-04-20). I did some digging in the mail archive this morning and\nfound [1] which seems to confirm the intention was to preserve existing\nempty commits, not keep those that become empty. This fits with the\ndiscussion [2] which lead to this feature. The same patch series that\nintroduced --keep-empty for rebase introduced the two cherry-pick flags\n--allow-empty and --keep-redundant-commits to distinguish between the\ntwo cases and used `cherry-pick --allow-empty` to implement `rebase\n--keep-empty` (and failed to provide a way to continue after stopping \nfor a conflict resolution)\n\n[1] https://lore.kernel.org/git/5006614E.8090601@viscovery.net/\n[2]\nhttps://lore.kernel.org/git/20120323185205.GA11916@hmsreliant.think-freely.org/\n\n>  The only place in the\n> documentation I could find that mentions the distinction at all was in\n> the \"Behavioral Differences\" section, which was written by me as part\n> of commit  0661e49aeb84 (\"git-rebase.txt: document behavioral\n> differences between modes\", 2015-06-27).  Quoting from that commit\n> message:\n> \n> \"(In fact, it's not clear -- to me at least -- that these differences\n> were even desirable or intentional.)  Document these differences.\"\n> \n> I cannot find anywhere else making a distinction in either the\n> documentation or the commit messages or even any code comments, which\n> has left me wondering whether the distinction was intentional at all.\n> Things like commit b00bf1c9a8dd (\"git-rebase: make\n> --allow-empty-message the default\", 2018-06-27) give heavy precedence\n> to the assumption that git-rebase is often more happenstance than\n> design when it comes to edge cases like these.\n> \n>> has distinct options for those two cases. If I've explicitly created an\n>> empty commit then I want to keep it but I don't want to keep commits\n>> that become empty because the changes they contain are already upstream.\n>>\n>> If we want an option that keeps commits that become empty (Off hand I\n>> don't know why we would though) we should consider if that option should\n>> disable --cherry-mark when we create the todo list so that it keeps all\n>> commits that become empty when they're rebased.\n> \n> To answer the particular reason I have seen why folks might want to\n> keep both commits that start empty and become empty, but still use\n> --cherry-mark to discard commits that are already upstream:\n>   * If cherry-mark can determine the commit was \"already upstream\",\n> then because of how cherry-mark works this means the upstream commit\n> message was about the *exact* same set of changes.  Thus, the commit\n> messages can be assumed to be fully interchangeable (and are in fact\n> likely to be completely identical).\n>   * If a commit does not start empty, but becomes so because its\n> changes were a subset of those upstream, then while there are clearly\n> no more changes that need to be applied, the commit messages can be\n> expected to differ -- either there will be an upstream commit that\n> included a bunch of other changes too, or the commit we are rebasing\n> will be spread across multiple upstream commits.  If the project has\n> bad commit message hygiene (because e.g. they are using atrocious code\n> review tools like GitHub that don't allow you to review them), there's\n> a reasonable likelihood that the upstream version only talks about the\n> other changes in the commit instead of the fix we are rebasing.  If\n> so, there may be useful information in this commit message that we\n> want to copy somewhere.\n\nThat sounds reasonable, (I'm not sure what one would do with the commit\nmessage though as creating an empty commit wont be any use for blame or\nsomeone looking at the commit that introduced the change upstream. One\ncould use add a note to the upstream commit but then there's the problem \nof sharing notes)\n\n> It's a somewhat rare usecase involving projects that generally aren't\n> careful with commit messages but which has some people working on it\n> who are careful.  But even for these projects, the odds that a commit\n> message will just get tossed anyway seem fairly high, so I will not\n> strongly defend this choice; it just seemed like \"--keep-empty\" or\n> \"--empty=keep\" should mean \"keep empty\", not \"sometimes keep empty\".\n> So, if it'd be nice if we could come up with clear wording if we are\n> going to implement other possibilities.\n> \n> Anyway, am I correctly understanding that you would like to see the\n> following matrix of options?\n>   * Drop commits that either start or become empty [the current --empty=drop]\n\nYes please\n\n>   * Pause and ask users whether they want commits that either start or\n> become empty [the current --empty=ask]\n\nI'd prefer an option to keep the commits that start empty and ask about\nthe ones that become empty (just like --keep-empty does now)\n\n>   * Keep commits that start empty, but drop ones that become empty [a\n> new option]\n\nYes please\n\n> and possibly drop the current --empty=keep behavior?\n\nYes as I'm not sure it's that useful, if users want to keep the\ninformation from the message of a commit that becomes empty I'm not sure\njust creating an empty commit is going to be much use in practice.\n\n>>> Note that traditionally, am-based rebases have always dropped commits\n>>> that either started or became empty, while interactive-based rebases\n>>> have defaulted to ask (and provided an option to keep commits that\n>>> started empty).  This difference made sense since users of an am-based\n>>> rebase just wanted to quickly batch apply a sequence of commits, while\n>>> users editing a todo list will likely want the chance to interact and\n>>> handle unusual cases on a case-by-case basis.\n>>\n>> I don't see why it makes sense to drop an empty commit that I've made\n>> just because it is being rebased.\n> \n> Are you arguing that keeping commits that started empty should\n> actually be the default, i.e. that all three rebase backends have\n> traditionally had the wrong default?\n\nYes, I think rebase should preserve the commits that are being rebased\nas far as possible and the current default is probably a historical accident\n\n>> I'm pretty sure the behavor of the\n>> am-based rebase is a function of `git am` not being able to create empty\n>> commits.\n> \n> That may be, but it's not quite clear.  Junio kind of commented on\n> this in https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/.\n> His comments that \"lack of --keep-empty on the 'am' side is probably a\n> bug that wants to be fixed\" might imply that he only sees it as a\n> useful option.  If the behavior of the am-based rebase dropping\n> commits which started empty was solely a function of git-am not being\n> able to create empty commits, then the correct fix would be to make\n> keeping the commits which started empty as the default or maybe only\n> behavior rather than just making it an option.\n\nYeah I'm not sure how Junio feels defaulting to keeping commits that\nstart empty\n\n> I'd actually be open to changing the default here.\n\nI'd be keen to do that but we should try and find out what others think.\n\n>  We have a\n> precedent in doing so with commit b00bf1c9a8dd (\"git-rebase: make\n> --allow-empty-message the default\", 2018-06-27) where we stated that\n> really rare edge cases (which I think both empty commit messages and\n> commits which start empty qualify as) probably have the wrong default\n> in rebase.  And changing the default would yield a much easier to\n> understand matrix of options:\n> \n>   * Drop commits that become empty (modification to --empty=drop in\n> that it keeps commits which started empty)\n>   * Pause and ask users if commits become empty (modification to\n> --empty=ask in that it keeps commits which started empty)\n>   * Keep commits that become empty, in addition to those that started\n> empty (--empty=keep).\n\nThat matrix is easier to understand and covers all the cases I'm \ninterested in.\n\n>>> However, not all rebases\n>>> using the interactive machinery are explicitly interactive anymore.  In\n>>> particular --merge was always meant to behave more like --am: just\n>>> rebase a batch of commits without popping up a todo list.\n>>>\n>>> If the --empty flag is not specified, pick defaults as follows:\n>>>     explicitly interactive: ask\n>>>     --exec: keep (exec is about checking existing commits, and often\n>>>                   used without actually changing the base.  Thus the\n>>>                   expectation is that the user doesn't necessarily want\n>>>                   anything to change; they just want to test).\n>>>     otherwise: drop\n>>\n>> I'm not sure I like changing the behavior based on --exec, I see what\n>> you're getting at but it has the potential to be confusing. What if I\n>> want to rearrange the commits without changing the base - why must I\n>> specify --empty=keep there but not if I add --exec to the command line?\n> \n> This was probably poorly explained and also poorly implemented.  Let\n> me try from a slightly different angle, and come to a slightly\n> different result than I did previously:\n> \n> Quoting from Junio in b00bf1c9a8dd (\"git-rebase: make\n> --allow-empty-message the default\", 2018-06-27),\n>    '\"am\" based rebase is solely to transplant an existing history and\n> want to stop much less than \"interactive\" one whose purpose is to\n> polish a series before making it publishable, and asking for\n> confirmation (\"this has become empty--do you want to drop it?\") is\n> more appropriate from the workflow point of view'\n> \n> I would modify Junio's wording to expand \"am based rebase\" to say that\n> if the user hasn't requested an interactive rebase or otherwise\n> explicitly signalled that they want some measure of interactivity\n> (e.g. specified --empty=ask), then we should not stop and ask how to\n> handle commits which started OR became empty.  In other worse, it's\n> not just the am-based rebase that is about transplanting commits but\n> any rebase where interactivity isn't requested.  I think the\n> traditional --keep-empty is quite broken in this regard since it does\n> stop and ask with commits that become empty.  rebase -m, rebase\n> --exec, etc. should not stop when a commit becomes empty, there should\n> be some default it just uses unless the user also adds\n> --interactive/-i/--empty=ask.\n> \n> But, if we take your above suggestion to make keeping the commits\n> which started empty not only the default but remove it from the\n> options, then this becomes much easier.  The choices become (kind of\n> repeated from above):\n> \n> [Keep the commits that started empty in all three cases, otherwise:]\n>   * Drop commits that become empty (--empty=drop)\n>   * Pause and ask users if commits become empty (--empty=ask)\n>   * Keep commits that become empty (--empty=keep)\n\nThat's nice - I like it\n\n> and the defaults are simply:\n>    --interactive/-i: --empty=ask\n>    everything else: --empty=drop\n\nWhile I see the point about the difference between -i and the other \nrebase modes any flavor of rebase can stop at any time due to conflicts \nso it can never be fully automatic. Given that commits becoming empty is \na bit of a corner case I'm not sure the complexity of different defaults \nis justified but I don't feel that strongly about it.\n\n> (And as a bonus, in the thread I've referenced twice above Junio\n> mentioned -- in respect to commits that become empty -- that he thinks\n> these are the right defaults.  He didn't comment on the behavior of\n> commits that start empty, which seems to be your primary concern.)\n\n>>> Also, this commit makes --keep-empty just imply --empty=keep, and hides\n>>> it from help so that we aren't confusing users with different ways to do\n>>> the same thing.  (I could have added a --drop-empty flag, but then that\n>>> invites users to specify both --keep-empty and --drop-empty and we have\n>>> to add sanity checking around that; it seems cleaner to have a single\n>>> multi-valued option.)  This actually fixes --keep-empty too; previously,\n>>> it only meant to sometimes keep empty commits, in particular commits\n>>> which started empty would be kept.  But it would still error out and ask\n>>> the user what to do with commits that became empty.  Now it keeps empty\n>>> commits, as instructed.\n>>\n>> It certainly changes the behavior of --keep-empty but I'm not sure it\n>> \"fixes\" it. If I have some empty commits I want to keep as placeholders\n>> then that's different from wanting to keep commits that become empty\n>> because their changes are upstream but --cherry-mark didn't detect them.\n>>\n>> In summary I'm in favor of making it easier to drop commits that become\n>> empty but not tying that to the handling of commits that are empty\n>> before they are rebased.\n> \n> Sounds like my modified proposal above may be in line with your tastes?\n\nYes. Keep commits that start empty and have an option to control what \nhappens to those that become empty (defaulting to --empty=ask for \ninteractive rebases at least).\n\n>> I'm also not happy that the deprecation of --keep-empty suddenly makes\n>> --no-keep-empty an error.\n> \n> We could translate that to --empty=drop; does that sound reasonable?\n\ntranslating it to --empty=ask would keep the behavior the same even if \nit sounds a bit odd. I don't think we want to change the behavior of \n--[no-]keep-empty while it is deprecated.\n\n> \n> [...]\n>>> @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n>>>       struct option options[] = {\n>>>               OPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n>>>                          REBASE_FORCE),\n>>> -             OPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n>>> +             { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>>> +                     N_(\"(DEPRECATED) keep empty commits\"),\n>>> +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n>>\n>> It is all very well deprecating --keep-empty but suddenly making\n>> '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure\n>> it's worth changing these options as I think the only user is\n>> git-rebase--preserve-merges.sh\n> \n> Side track: Since git-rebase--preserve-merges.sh is deprecated and we\n> want to get rid of it, and rebase-merges exists and is a better\n> implementation of the original idea, can we just translate rebase -p\n> into rebase -r and delete git-rebase--preserve-merges.sh?  (With a few\n> wrinkles, such as telling users in the middle of an existing\n> preserve-merges-rebase that they either need to use an old version of\n> git to continue their rebase or else abort the rebase?)\n\ndscho posted some patches in November or December moving along this \npath, I'm not sure what the outcome was. It's a bit complicated by the \ndifferent todo list formats between -p and -r but I think the end game \nshould be to treat -p as -r as you suggest.\n\n> \n>> Best Wishes\n>>\n>> Phillip\n>>\n> [...]\n>> This function is used by cherry-pick/revert not rebase do we need to\n>> change it?\n> \n> Possibly not; I can take a look.  I had quite a difficult time trying\n> to figure out how to make sure that --empty options were saved and\n> restored across rebase --continue; it's not at all unlikely that I\n> modified more than I needed to \"for consistency\".\n> \n> [...]\n>>> @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)\n>>\n>> again this is for cherry-pick/revert\n> \n> Okay, will double check this too.\n> \n> [...]\n> \n\n"},{"id":"389478","messageId":"9ac52ef1-c1cb-45aa-178a-ec5a282bd761@gmail.com","threadId":"52496","inReplyTo":"xmqqy2uj3u3q.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-08T14:32:56Z","receivedAt":"2020-01-08T14:33:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 07/01/2020 20:11, Junio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n> \n>> However, I thought of this option before Junio suggested a\n>> rebase.backend config setting, so we could just rely on that instead.\n>> Thus, getting rid of the \"--am\" flag in detail would mean:\n>>    * I need to redo the test changes in this series to use \"-c\n>> rebase.backend=am\" instead of \"--am\"\n>>    * It will be slightly harder for users to use the escape hatch in\n>> one-off cases during the transition\n>>    * We need to figure out the right way to reword the documentation\n>>\n>> The first two are pretty minor, so that probably means I just need to\n>> come up with some good wording for the documentation (suggestions\n>> welcome...)\n> \n> It probably is a good idea to keep --am (and possibly --[no-]am) as\n> long as rebase.backend option exists.  A configuration variable that\n> changes behaviour without allowing it to be overridden from the\n> command line is not a good idea.\n\nI view this change in the default backend as similar to the rewrite in C \nin that it is an implementation detail we're changing that should be \ntransparent (and beneficial in terms of performance) to the user. There \nwe provided a configuration variable but not a command line option to \ncontrol if it was used or not. I don't see it as something the user will \nwant to change from rebase to rebase but perhaps I'm missing something.\n\nBest Wishes\n\nPhillip\n"},{"id":"389486","messageId":"xmqqmuax3lzv.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"9ac52ef1-c1cb-45aa-178a-ec5a282bd761@gmail.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-01-08T17:18:28Z","receivedAt":"2020-01-08T17:18:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I view this change in the default backend as similar to the rewrite in\n> C in that it is an implementation detail we're changing that should be\n> transparent (and beneficial in terms of performance) to the\n> user. There we provided a configuration variable but not a command\n> line option to control if it was used or not.\n\nDo you mean things like GIT_TEST_ADD_I_USE_BUILTIN?  I think it is\nOK to have such an escape mechanism to allow people to opt out of\na new reimplementation until it matures, but I do not offhand recall\ndoing so with config.\n"},{"id":"389494","messageId":"fabb4048-41fa-0b24-1716-e63237478956@gmail.com","threadId":"52496","inReplyTo":"xmqqmuax3lzv.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 10/15] rebase: add an --am option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-08T18:55:53Z","receivedAt":"2020-01-08T18:55:55Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 08/01/2020 17:18, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> I view this change in the default backend as similar to the rewrite in\n>> C in that it is an implementation detail we're changing that should be\n>> transparent (and beneficial in terms of performance) to the\n>> user. There we provided a configuration variable but not a command\n>> line option to control if it was used or not.\n> \n> Do you mean things like GIT_TEST_ADD_I_USE_BUILTIN?  I think it is\n> OK to have such an escape mechanism to allow people to opt out of\n> a new reimplementation until it matures, but I do not offhand recall\n> doing so with config.\n\nI was thinking of rebase.usebuiltin\n\nBest Wishes\n\nPhillip\n"},{"id":"389549","messageId":"nycvar.QRO.7.76.6.2001092230360.46@tvgsbejvaqbjf.bet","threadId":"52496","inReplyTo":"d9d3d360-fc45-a716-4b35-4367c017460e@gmail.com","subject":"Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-01-09T21:32:55Z","receivedAt":"2020-01-09T21:33:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 8 Jan 2020, Phillip Wood wrote:\n\n> On 07/01/2020 19:15, Elijah Newren wrote:\n> >\n> > On Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com>\n> > wrote:\n> >\n> > > On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:\n> > >\n> > [...]\n> > > > @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char\n> > > > **argv, const char *prefix)\n> > > >       struct option options[] = {\n> > > >               OPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n> > > >                          REBASE_FORCE),\n> > > > -             OPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty\n> > > > commits\")),\n> > > > +             { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> > > > +                     N_(\"(DEPRECATED) keep empty commits\"),\n> > > > +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG |\n> > > > PARSE_OPT_HIDDEN,\n> > >\n> > > It is all very well deprecating --keep-empty but suddenly making\n> > > '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure\n> > > it's worth changing these options as I think the only user is\n> > > git-rebase--preserve-merges.sh\n> >\n> > Side track: Since git-rebase--preserve-merges.sh is deprecated and we\n> > want to get rid of it, and rebase-merges exists and is a better\n> > implementation of the original idea, can we just translate rebase -p\n> > into rebase -r and delete git-rebase--preserve-merges.sh?  (With a few\n> > wrinkles, such as telling users in the middle of an existing\n> > preserve-merges-rebase that they either need to use an old version of\n> > git to continue their rebase or else abort the rebase?)\n>\n> dscho posted some patches in November or December moving along this path, I'm\n> not sure what the outcome was. It's a bit complicated by the different todo\n> list formats between -p and -r but I think the end game should be to treat -p\n> as -r as you suggest.\n\nI sent some patches to switch `git svn` over to `rebase -r`, but that was\nonly in non-interactive mode.\n\nIn interactive mode, the todo lists indeed look too different and I think\nwe'll just need to wait a year or so and then we can just delete\n`git-rebase--preserve-merges.sh`.\n\nCiao,\nDscho\n"},{"id":"389601","messageId":"20200110231436.GA24315@google.com","threadId":"52496","inReplyTo":"044853fd612ee8cf6928bb7c4ebb1eacaa172aa3.1577217299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-10T23:14:36Z","receivedAt":"2020-01-10T23:14:42Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nElijah Newren via GitGitGadget wrote:\n\n> The am-backend drops information and thus limits what we can do:\n>\n>   * lack of full tree information from the original commits means we\n>     cannot do directory rename detection and warn users that they might\n>     want to move some of their new files that they placed in old\n>     directories to prevent their becoming orphaned.[1]\n>   * reduction in context from only having a few lines beyond those\n>     changed means that when context lines are non-unique we can apply\n>     patches incorrectly.[2]\n>   * lack of access to original commits means that conflict marker\n>     annotation has less information available.\n>\n> Also, the merge/interactive backend have far more abilities, appear to\n> currently have a slight performance advantage[3] and have room for more\n> optimizations than the am backend[4] (and work is underway to take\n> advantage of some of those possibilities).\n>\n> [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n> [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n> [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n> [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Documentation/git-rebase.txt           |  2 +-\n>  builtin/rebase.c                       |  4 ++--\n>  t/t5520-pull.sh                        | 10 ++++++----\n>  t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n>  4 files changed, 11 insertions(+), 8 deletions(-)\n\nThanks for writing this.  We finally rolled this out to our internal\npopulation at $DAYJOB and ran into a couple of issues:\n\n 1. \"git rebase --am\" does not invoke the post-commit hook, but \"git\n    rebase --merge\" does.  Is this behavior change intended?\n\n    Noticed because jiri[1] installs a post-commit hook that warns\n    about commits on detached HEAD, so this change makes rebases more\n    noisy in repositories that were set up using jiri.\n\n 2. GIT_REFLOG_ACTION contains \"rebase -i\" even though the rebase is\n    not interactive.\n\n 3. In circumstances I haven't pinned down yet, we get the error\n    message \"invalid date format: @@2592000 +0000\":\n\n\t$ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L\n\t$ git checkout --theirs file\n\t$ git add file\n\t$ git rebase --continue\n\tfatal: invalid date format: @@2592000 +0000\n\terror: could not commit staged changes.\n\n    This isn't reproducible without --committer-date-is-author-date.\n    More context (the test where it happens) is in [2].\n\n 4. I suspect the exit status in the \"you need to resolve conflicts\"\n    case has changed.  With rebase --am, [3] would automatically\n    invoke rebase --abort when conflicts are present, but with rebase\n    --merge it does not.\n\nKnown?\n\nThanks,\nJonathan\n\n[1] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/manifest.go#1347\n[2] https://source.chromium.org/chromium/chromium/tools/depot_tools/+/master:tests/git_common_test.py;l=721;drc=6b52dc21e166c46707b4c8eb26c74c70d4f9977e;bpv=1;bpt=0\n[3] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/project.go#1664\n"},{"id":"389603","messageId":"CABPp-BEGwJ=0+6TMYXvMzpds0h6bz4gZA8Pi95SH9M4CKBtfOw@mail.gmail.com","threadId":"52496","inReplyTo":"20200110231436.GA24315@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-11T01:16:11Z","receivedAt":"2020-01-11T01:16:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Jonathan,\n\nOn Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> Hi,\n>\n> Elijah Newren via GitGitGadget wrote:\n>\n> > The am-backend drops information and thus limits what we can do:\n> >\n> >   * lack of full tree information from the original commits means we\n> >     cannot do directory rename detection and warn users that they might\n> >     want to move some of their new files that they placed in old\n> >     directories to prevent their becoming orphaned.[1]\n> >   * reduction in context from only having a few lines beyond those\n> >     changed means that when context lines are non-unique we can apply\n> >     patches incorrectly.[2]\n> >   * lack of access to original commits means that conflict marker\n> >     annotation has less information available.\n> >\n> > Also, the merge/interactive backend have far more abilities, appear to\n> > currently have a slight performance advantage[3] and have room for more\n> > optimizations than the am backend[4] (and work is underway to take\n> > advantage of some of those possibilities).\n> >\n> > [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n> > [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n> > [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n> > [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n> >\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >  Documentation/git-rebase.txt           |  2 +-\n> >  builtin/rebase.c                       |  4 ++--\n> >  t/t5520-pull.sh                        | 10 ++++++----\n> >  t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n> >  4 files changed, 11 insertions(+), 8 deletions(-)\n>\n> Thanks for writing this.  We finally rolled this out to our internal\n> population at $DAYJOB and ran into a couple of issues:\n\nCool, thanks for testing it out.\n\n>  1. \"git rebase --am\" does not invoke the post-commit hook, but \"git\n>     rebase --merge\" does.  Is this behavior change intended?\n>\n>     Noticed because jiri[1] installs a post-commit hook that warns\n>     about commits on detached HEAD, so this change makes rebases more\n>     noisy in repositories that were set up using jiri.\n\nI've never used a post-commit hook or seen one in the wild.  Certainly\nwasn't intentional, but it's not clear to me if it's wrong or right\neither.  I don't see why it would make sense to distinguish between\nany of git rebase --am/--merge/--interactive, but it isn't too\nsurprising that by historical accident the two rebase backends which\nhappened to call git-commit behind the scenes would call a post-commit\nhook and the other rebase backend that didn't call git-commit\nwouldn't.\n\nBut the big question here, is what is correct behavior?  Should rebase\ncall the post-commit hook, or should it skip it?  I haven't any clue\nwhat the answer to that is.\n\n>  2. GIT_REFLOG_ACTION contains \"rebase -i\" even though the rebase is\n>     not interactive.\n\nYep, as does --keep, --exec, --rebase-merges, etc.  There are lots of\nrebases which use the interactive machinery even if they aren't\nexplicitly interactive.  I've never seen the \"-i\" in the reflog\nmessage defined, but clearly it has always been used whenever the\ninteractive machinery was in play regardless of whether the rebase was\ninteractive.  In that regard, I figured that --merge fit in rather\nnicely.  (And I noted the fact that reflog messages were different\nbetween the backends among the \"BEHAVIORAL DIFFERENCES\" section of\ngit-rebase.txt).  But if others think we should just drop the -i (much\nas we did for the bash prompt), I'd be happy with that too.  If we go\nthat route, I think I'd rather drop the -i in the reflog for all\nrebases, not just the\nusing-the-interactive-machinery-but-not-explicitly-interactive ones.\n\n>  3. In circumstances I haven't pinned down yet, we get the error\n>     message \"invalid date format: @@2592000 +0000\":\n>\n>         $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L\n>         $ git checkout --theirs file\n>         $ git add file\n>         $ git rebase --continue\n>         fatal: invalid date format: @@2592000 +0000\n>         error: could not commit staged changes.\n>\n>     This isn't reproducible without --committer-date-is-author-date.\n>     More context (the test where it happens) is in [2].\n\nInteresting.  Do you happen to know if this started happening with\nra/rebase-i-more-options, or did it just become an issue with\nen/rebase-backend?  I looked around at the link you provided and feel\na bit confused; I'm not sure which test does this or how I'd\nreproduce.\n\n>  4. I suspect the exit status in the \"you need to resolve conflicts\"\n>     case has changed.  With rebase --am, [3] would automatically\n>     invoke rebase --abort when conflicts are present, but with rebase\n>     --merge it does not.\n>\n> Known?\n\nNope, but I would certainly hope that \"you need to resolve conflicts\"\nwould result in a non-zero exit status.  If it doesn't, that sounds\nlike a bug in the interactive backend that we need to fix.  I'll dig\nin.\n\n\n\nThanks for the reports!\nElijah\n"},{"id":"389611","messageId":"052fdedc-2beb-91ab-c5c3-53fb99e64810@gmail.com","threadId":"52496","inReplyTo":"CABPp-BEGwJ=0+6TMYXvMzpds0h6bz4gZA8Pi95SH9M4CKBtfOw@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-11T14:41:36Z","receivedAt":"2020-01-11T14:45:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Jonathan & Elijah\n\nOn 11/01/2020 01:16, Elijah Newren wrote:\n> Hi Jonathan,\n> \n> On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>>\n>> Hi,\n>>\n>> Elijah Newren via GitGitGadget wrote:\n>>\n>>> The am-backend drops information and thus limits what we can do:\n>>>\n>>>    * lack of full tree information from the original commits means we\n>>>      cannot do directory rename detection and warn users that they might\n>>>      want to move some of their new files that they placed in old\n>>>      directories to prevent their becoming orphaned.[1]\n>>>    * reduction in context from only having a few lines beyond those\n>>>      changed means that when context lines are non-unique we can apply\n>>>      patches incorrectly.[2]\n>>>    * lack of access to original commits means that conflict marker\n>>>      annotation has less information available.\n>>>\n>>> Also, the merge/interactive backend have far more abilities, appear to\n>>> currently have a slight performance advantage[3] and have room for more\n>>> optimizations than the am backend[4] (and work is underway to take\n>>> advantage of some of those possibilities).\n>>>\n>>> [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n>>> [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n>>> [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n>>> [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n>>>\n>>> Signed-off-by: Elijah Newren <newren@gmail.com>\n>>> ---\n>>>   Documentation/git-rebase.txt           |  2 +-\n>>>   builtin/rebase.c                       |  4 ++--\n>>>   t/t5520-pull.sh                        | 10 ++++++----\n>>>   t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n>>>   4 files changed, 11 insertions(+), 8 deletions(-)\n>>\n>> Thanks for writing this.  We finally rolled this out to our internal\n>> population at $DAYJOB and ran into a couple of issues:\n> \n> Cool, thanks for testing it out.\n> \n>>   1. \"git rebase --am\" does not invoke the post-commit hook, but \"git\n>>      rebase --merge\" does.  Is this behavior change intended?\n>>\n>>      Noticed because jiri[1] installs a post-commit hook that warns\n>>      about commits on detached HEAD, so this change makes rebases more\n>>      noisy in repositories that were set up using jiri.\n\nPerhaps that hook could learn not to warn if a branch is being rebased? \ngit could be more helpful there by having a porcelain option to status \nthat prints the branch name if we're rebasing (`git worktree --list` \nshows the branch correctly when it's being rebased but does not (yet - I \nhave a patch to do it) mark the current worktree so isn't very helpful.)\n\n> I've never used a post-commit hook or seen one in the wild.  Certainly\n> wasn't intentional, but it's not clear to me if it's wrong or right\n> either.  I don't see why it would make sense to distinguish between\n> any of git rebase --am/--merge/--interactive, but it isn't too\n> surprising that by historical accident the two rebase backends which\n> happened to call git-commit behind the scenes would call a post-commit\n> hook and the other rebase backend that didn't call git-commit\n> wouldn't.\n\nLooking through the history the am based rebase has never run the \npost-commit hook as am has its own set of hooks and the scripted version \nused commit-tree. The merge based rebase ran `git commit` which ran the \npost commit hook. The interactive rebase ran the hook until and I broke \nit in a356ee4659b (\"sequencer: try to commit without forking 'git \ncommit'\", 2017-11-24) and after I fixed it in 4627bc777e (\"sequencer: \nrun post-commit hook\", 2019-10-15). As it was broken for two years with \nno one noticing it can't be that popular.\n\n> But the big question here, is what is correct behavior?  Should rebase\n> call the post-commit hook, or should it skip it?  I haven't any clue\n> what the answer to that is.\n\nIt's creating a new commit so I lean towards thinking it should run the \npost-commit hook. As an example I have a post-commit hook that prints a \nwarning if a commit is created on a branch that is being rewritten by \none of my scripts in another worktree. There are pre-commit and \npre-rebase hooks to try and prevent that, but the warning is there as a \nlast resort if those hooks are by-passed.\n\n>>   2. GIT_REFLOG_ACTION contains \"rebase -i\" even though the rebase is\n>>      not interactive.\n\nIf this is important to people I think it should be easy enough to set \nGIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long \nas it hasn't already been set by the user) rather than relying on \nsequencer.c to do it.\n\n> Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of\n> rebases which use the interactive machinery even if they aren't\n> explicitly interactive.  I've never seen the \"-i\" in the reflog\n> message defined, but clearly it has always been used whenever the\n> interactive machinery was in play regardless of whether the rebase was\n> interactive.  In that regard, I figured that --merge fit in rather\n> nicely.  (And I noted the fact that reflog messages were different\n> between the backends among the \"BEHAVIORAL DIFFERENCES\" section of\n> git-rebase.txt).  But if others think we should just drop the -i (much\n> as we did for the bash prompt), I'd be happy with that too.  If we go\n> that route, I think I'd rather drop the -i in the reflog for all\n> rebases, not just the\n> using-the-interactive-machinery-but-not-explicitly-interactive ones.\n> \n>>   3. In circumstances I haven't pinned down yet, we get the error\n>>      message \"invalid date format: @@2592000 +0000\":\n>>\n>>          $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L\n>>          $ git checkout --theirs file\n>>          $ git add file\n>>          $ git rebase --continue\n>>          fatal: invalid date format: @@2592000 +0000\n>>          error: could not commit staged changes.\n>>\n>>      This isn't reproducible without --committer-date-is-author-date.\n>>      More context (the test where it happens) is in [2].\n> \n> Interesting.  Do you happen to know if this started happening with\n> ra/rebase-i-more-options, or did it just become an issue with\n> en/rebase-backend?  I looked around at the link you provided and feel\n> a bit confused; I'm not sure which test does this or how I'd\n> reproduce.\n\nI'm confused by the test as well. As ra/rebase-i-more-options only \ntouched the sequencer then any bugs would only show up in this test \n(which runs a non-interactive rebase) once en/rbease-backend switched to \nthat backend. It seems likely that ra/rebase-i-more-options is to blame.\n\nJonathan - do you happen to know if your users create empty commits at \nall? and if so what do they expect rebase to do with them (and any that \nbecome empty when they are rebased) - cf \nhttps://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>\n\nBest Wishes\n\nPhillip\n\n>>   4. I suspect the exit status in the \"you need to resolve conflicts\"\n>>      case has changed.  With rebase --am, [3] would automatically\n>>      invoke rebase --abort when conflicts are present, but with rebase\n>>      --merge it does not.\n>>\n>> Known?\n> \n> Nope, but I would certainly hope that \"you need to resolve conflicts\"\n> would result in a non-zero exit status.  If it doesn't, that sounds\n> like a bug in the interactive backend that we need to fix.  I'll dig\n> in.\n> \n> \n> \n> Thanks for the reports!\n> Elijah\n> \n"},{"id":"389634","messageId":"nycvar.QRO.7.76.6.2001120942460.46@tvgsbejvaqbjf.bet","threadId":"52496","inReplyTo":"052fdedc-2beb-91ab-c5c3-53fb99e64810@gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-01-12T17:59:27Z","receivedAt":"2020-01-12T18:00:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 11 Jan 2020, Phillip Wood wrote:\n\n> On 11/01/2020 01:16, Elijah Newren wrote:\n> >\n> > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n> > >\n> > > Elijah Newren via GitGitGadget wrote:\n> > >\n> > >   1. \"git rebase --am\" does not invoke the post-commit hook, but \"git\n> > >      rebase --merge\" does.  Is this behavior change intended?\n> > >\n> > >      Noticed because jiri[1] installs a post-commit hook that warns\n> > >      about commits on detached HEAD, so this change makes rebases more\n> > >      noisy in repositories that were set up using jiri.\n>\n> Perhaps that hook could learn not to warn if a branch is being rebased?\n> git could be more helpful there by having a porcelain option to status\n> that prints the branch name if we're rebasing (`git worktree --list`\n> shows the branch correctly when it's being rebased but does not (yet - I\n> have a patch to do it) mark the current worktree so isn't very helpful.)\n>\n> > I've never used a post-commit hook or seen one in the wild.  Certainly\n> > wasn't intentional, but it's not clear to me if it's wrong or right\n> > either.  I don't see why it would make sense to distinguish between\n> > any of git rebase --am/--merge/--interactive, but it isn't too\n> > surprising that by historical accident the two rebase backends which\n> > happened to call git-commit behind the scenes would call a post-commit\n> > hook and the other rebase backend that didn't call git-commit\n> > wouldn't.\n>\n> Looking through the history the am based rebase has never run the post-commit\n> hook as am has its own set of hooks and the scripted version used commit-tree.\n> The merge based rebase ran `git commit` which ran the post commit hook. The\n> interactive rebase ran the hook until and I broke it in a356ee4659b\n> (\"sequencer: try to commit without forking 'git commit'\", 2017-11-24) and\n> after I fixed it in 4627bc777e (\"sequencer: run post-commit hook\",\n> 2019-10-15). As it was broken for two years with no one noticing it can't be\n> that popular.\n\nMaybe a crazy idea, but maybe not: how about running the `post-commit`\nhook _only_ if `--merge` was specified explicitly, and in that case (and\nguarded behind a check verifying that the `post-commit` hook _actually_\nexists _and_ is executable) warn the user that this hook won't be run in\nfuture versions?\n\nTo make things better for users who actually want to run that hook during\nrebases, we could introduce a config option, say,\n`rebase.runPostCommitHook` that is a tri-state (`true`, `false`,\n`onlyForDashDashMerge`, at first defaulting to the last, eventually to\n`false`).\n\nCrazy? Or helpful?\n\n> > But the big question here, is what is correct behavior?  Should rebase\n> > call the post-commit hook, or should it skip it?  I haven't any clue\n> > what the answer to that is.\n>\n> It's creating a new commit so I lean towards thinking it should run the\n> post-commit hook. As an example I have a post-commit hook that prints a\n> warning if a commit is created on a branch that is being rewritten by\n> one of my scripts in another worktree. There are pre-commit and\n> pre-rebase hooks to try and prevent that, but the warning is there as a\n> last resort if those hooks are by-passed.\n\nI guess you're right, it is quite surprising that the `post-commit` hook\nis _not_ run for `--am` rebases even though commits are created.\n\n> > >   2. GIT_REFLOG_ACTION contains \"rebase -i\" even though the rebase is\n> > >      not interactive.\n>\n> If this is important to people I think it should be easy enough to set\n> GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long\n> as it hasn't already been set by the user) rather than relying on\n> sequencer.c to do it.\n\nI agree (but won't have time to implement it, so maybe I should shut up\nalready...)\n\n> > Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of\n> > rebases which use the interactive machinery even if they aren't\n> > explicitly interactive.  I've never seen the \"-i\" in the reflog\n> > message defined, but clearly it has always been used whenever the\n> > interactive machinery was in play regardless of whether the rebase was\n> > interactive.  In that regard, I figured that --merge fit in rather\n> > nicely.  (And I noted the fact that reflog messages were different\n> > between the backends among the \"BEHAVIORAL DIFFERENCES\" section of\n> > git-rebase.txt).  But if others think we should just drop the -i (much\n> > as we did for the bash prompt), I'd be happy with that too.  If we go\n> > that route, I think I'd rather drop the -i in the reflog for all\n> > rebases, not just the\n> > using-the-interactive-machinery-but-not-explicitly-interactive ones.\n> >\n> > >   3. In circumstances I haven't pinned down yet, we get the error\n> > >      message \"invalid date format: @@2592000 +0000\":\n> > >\n> > >          $ git rebase --committer-date-is-author-date --onto branch_K\n> > >          branch_L~1 branch_L\n> > >          $ git checkout --theirs file\n> > >          $ git add file\n> > >          $ git rebase --continue\n> > >          fatal: invalid date format: @@2592000 +0000\n> > >          error: could not commit staged changes.\n> > >\n> > >      This isn't reproducible without --committer-date-is-author-date.\n> > >      More context (the test where it happens) is in [2].\n> >\n> > Interesting.  Do you happen to know if this started happening with\n> > ra/rebase-i-more-options, or did it just become an issue with\n> > en/rebase-backend?  I looked around at the link you provided and feel\n> > a bit confused; I'm not sure which test does this or how I'd\n> > reproduce.\n>\n> I'm confused by the test as well. As ra/rebase-i-more-options only touched the\n> sequencer then any bugs would only show up in this test (which runs a\n> non-interactive rebase) once en/rbease-backend switched to that backend. It\n> seems likely that ra/rebase-i-more-options is to blame.\n>\n> Jonathan - do you happen to know if your users create empty commits at all?\n> and if so what do they expect rebase to do with them (and any that become\n> empty when they are rebased) - cf\n> https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>\n\nThe double `@` looks very funny. I would be interested in seeing an MCVE.\n\n> > >   4. I suspect the exit status in the \"you need to resolve conflicts\"\n> > >      case has changed.  With rebase --am, [3] would automatically\n> > >      invoke rebase --abort when conflicts are present, but with rebase\n> > >      --merge it does not.\n> > >\n> > > Known?\n> >\n> > Nope, but I would certainly hope that \"you need to resolve conflicts\"\n> > would result in a non-zero exit status.  If it doesn't, that sounds\n> > like a bug in the interactive backend that we need to fix.  I'll dig\n> > in.\n\nYes, exiting with status 0 would be a major bug, and I think it might even\nbe a bug that was introduced by me when I re-implemented the core loop of\nthe interactive rebase in C.\n\nBut to me it sounds as if 4. is not so about the exit code but about\naborting immediately. I do not recall seeing --am rebases to abort,\nthough, but to exit with error (and I saw the same behavior in interactive\nrebases).\n\nWe will need to see a reduced concrete example (preferably as a new test\ncase) of the described behavior.\n\nCiao,\nDscho\n"},{"id":"389642","messageId":"xmqqa76sl67u.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"052fdedc-2beb-91ab-c5c3-53fb99e64810@gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-01-12T21:23:17Z","receivedAt":"2020-01-12T21:23:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Looking through the history the am based rebase has never run the\n> post-commit hook as am has its own set of hooks and the scripted\n> version used commit-tree.\n>\n>> But the big question here, is what is correct behavior?  Should rebase\n>> call the post-commit hook, or should it skip it?  I haven't any clue\n>> what the answer to that is.\n>\n> It's creating a new commit so I lean towards thinking it should run\n> the post-commit hook. As an example I have a post-commit hook that\n\nThe reason why \"am\" did not run \"post-commit\" is because\n\"post-commit\" was (originally) meant to be for cases where the end\nuser types \"git commit\", IOW, the notion of \"since we are creating a\nnew commit object, let's run the post-commit hook\" used to be\ntotally misguided way of thinking.  The hook was \"the user _ran_\n'commit', so post-commit is run immediately afterwards\".\n\nThese days, I think most of our tools subscribe to the newer \"a\ncommit got created--have hook the chance to report it\" idea, simply\nbecause the original position is untenable---\"git merge\" may not\nwant to run the post-commit hook according to the initial design,\nand it is fine to make it not to run it when clean automerge was\nrecorded, but sometimes the end users have to type \"git commit\" to\nconclude a conflicted merge.\n\nSo from that point of view, I do not think it is too bad to bring\n\"am\" into the new ear and start running post-commit from it.\n\nThe \"pre-commit\" hook is a different story, though.  From purely\npractical point of view, starting to run extra verification that may\nveto new commits from getting created only because we changed the\nimplementation detail for some reason is a disservice to the users.\n\n"},{"id":"389815","messageId":"20200115195011.GA146834@google.com","threadId":"52496","inReplyTo":"052fdedc-2beb-91ab-c5c3-53fb99e64810@gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-15T19:50:11Z","receivedAt":"2020-01-15T19:50:16Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nPhillip Wood wrote:\n> On 11/01/2020 01:16, Elijah Newren wrote:\n\n>> But the big question here, is what is correct behavior?  Should rebase\n>> call the post-commit hook, or should it skip it?  I haven't any clue\n>> what the answer to that is.\n>\n> It's creating a new commit so I lean towards thinking it should run the\n> post-commit hook. As an example I have a post-commit hook that prints a\n> warning if a commit is created on a branch that is being rewritten by one of\n> my scripts in another worktree. There are pre-commit and pre-rebase hooks to\n> try and prevent that, but the warning is there as a last resort if those\n> hooks are by-passed.\n\nThe initial design for the post-commit hook was\n\n    commit 89e2c5f17b901edf28a8bb778ece3f6c18bbb721\n    Author: Junio C Hamano <gitster@pobox.com>\n    Date:   Thu Aug 18 17:20:08 2005 -0700\n\n    Add commit hook and make the verification customizable.\n\n    There are three hooks:\n\n        - 'pre-commit' is given an opportunity to inspect what is\n          being committed, before we invoke the EDITOR for the\n          commit message;\n\n        - 'commit-msg' is invoked on the commit log message after\n          the user prepares it;\n\n        - 'post-commit' is run after a successful commit is made.\n\n    The first two can interfere to stop the commit.  The last one is\n    for after-the-fact notification.\n\nThe initial implementation was only in \"git commit\" and didn't affect\nother commands, but that's an artifact of implementation, not\nintention.  The intention is more murky: certainly \"creating a new\ncommit\" is not the event we want to notify about (for example, \"git\ncommit-tree\" should not invoke the hook), but if I were designing it\ntoday then all operations that create a new commit and update the\ncurrent branch with it might qualify.\n\nEven that definition is a bit fuzzy: when I run \"git rebase <upstream>\n<branch>\", am I updating <branch> or the current branch?  Are\ncherry-picks that carry over changes that were committed previously\nnew commits?\n\nWorse, we have the body of existing post-commit hooks to contend with,\nand each one may have made different assumptions about the semantics.\nWe do not have the luxury of designing the hook without regard to that\nbody of existing hooks today.\n\nThe hook that jiri installs is very simple.  It wants to check when a\nuser has (interactively) made a commit on a detached HEAD, to let them\nknow that they might want to use \"git checkout -b\" afterward.  With\nthis particular hook, the behavior is better when git rebase does not\ninvoke the hook, since in the context of a rebase, the user has no\nneed to use \"git checkout -b\" afterward.  This is a regression, and I\nthink we need to take that seriously.\n\nIt doesn't seem like jiri is doing anything weird here, so there's a\nreasonably high probability that other hooks would be affected in the\nsame way.  How can we help authors of such hooks to handle this\nchange?\n\nI don't think that starting to invoke the 'post-commit' hook even more\nwithout going through that thought process is an acceptable fix.\nSince the \"right\" semantics aren't obvious here, the first step is\nprobably to collect some typical examples of post-commit hooks, like\nthe example from the message I'm replying to (thank you!).  Ideas for\nwhere we can find more?\n\nThanks,\nJonathan\n"},{"id":"389825","messageId":"20200115215922.GI181522@google.com","threadId":"52496","inReplyTo":"20200115195011.GA146834@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-01-15T21:59:22Z","receivedAt":"2020-01-15T21:59:30Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Jan 15, 2020 at 07:50:11PM +0000, Jonathan Nieder wrote:\n> Hi,\n> \n> Phillip Wood wrote:\n> > On 11/01/2020 01:16, Elijah Newren wrote:\n> \n> >> But the big question here, is what is correct behavior?  Should rebase\n> >> call the post-commit hook, or should it skip it?  I haven't any clue\n> >> what the answer to that is.\n> >\n> > It's creating a new commit so I lean towards thinking it should run the\n> > post-commit hook. As an example I have a post-commit hook that prints a\n> > warning if a commit is created on a branch that is being rewritten by one of\n> > my scripts in another worktree. There are pre-commit and pre-rebase hooks to\n> > try and prevent that, but the warning is there as a last resort if those\n> > hooks are by-passed.\n> \n> The initial design for the post-commit hook was\n> \n>     commit 89e2c5f17b901edf28a8bb778ece3f6c18bbb721\n>     Author: Junio C Hamano <gitster@pobox.com>\n>     Date:   Thu Aug 18 17:20:08 2005 -0700\n> \n>     Add commit hook and make the verification customizable.\n> \n>     There are three hooks:\n> \n>         - 'pre-commit' is given an opportunity to inspect what is\n>           being committed, before we invoke the EDITOR for the\n>           commit message;\n> \n>         - 'commit-msg' is invoked on the commit log message after\n>           the user prepares it;\n> \n>         - 'post-commit' is run after a successful commit is made.\n> \n>     The first two can interfere to stop the commit.  The last one is\n>     for after-the-fact notification.\n> \n> The initial implementation was only in \"git commit\" and didn't affect\n> other commands, but that's an artifact of implementation, not\n> intention.  The intention is more murky: certainly \"creating a new\n> commit\" is not the event we want to notify about (for example, \"git\n> commit-tree\" should not invoke the hook), but if I were designing it\n> today then all operations that create a new commit and update the\n> current branch with it might qualify.\n> \n> Even that definition is a bit fuzzy: when I run \"git rebase <upstream>\n> <branch>\", am I updating <branch> or the current branch?  Are\n> cherry-picks that carry over changes that were committed previously\n> new commits?\n> \n> Worse, we have the body of existing post-commit hooks to contend with,\n> and each one may have made different assumptions about the semantics.\n> We do not have the luxury of designing the hook without regard to that\n> body of existing hooks today.\n\nThis seems like the most important point to me. I took a very cursory\npeek at some search results on Github\n(https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code)\nand I see mostly shims - make sure certain files were included in the\ncommit, bring a worktree up to date, stage stuff that wasn't committed -\nbut again, that's a very brief glance at over 19000 probable post-commit\nhooks. Of course it's not possible for us to examine all of them, but\nhaving these 19k scripts being run lots of times when they didn't used\nto during an operation as common as rebase seems a little scary.\n\n> The hook that jiri installs is very simple.  It wants to check when a\n> user has (interactively) made a commit on a detached HEAD, to let them\n> know that they might want to use \"git checkout -b\" afterward.  With\n> this particular hook, the behavior is better when git rebase does not\n> invoke the hook, since in the context of a rebase, the user has no\n> need to use \"git checkout -b\" afterward.  This is a regression, and I\n> think we need to take that seriously.\n> \n> It doesn't seem like jiri is doing anything weird here, so there's a\n> reasonably high probability that other hooks would be affected in the\n> same way.  How can we help authors of such hooks to handle this\n> change?\n\nThis part I do disagree with. Git warns (advice-optional) when I enter\ndetached HEAD state. When I leave my detached HEAD with some orphaned\ncommits within (via checkout), I get a warning unconditionally, and an\nadvice-optional tip to use 'git branch <name> <commit-id>'. The Jiri\nhook is warning that these commits I'm making could be lost, but in fact\nGit itself is telling me that (and how to save them) at the first moment\nwhen they are actually in danger. So I think this particular example is\nnot a good one.\n\nI'm more worried about ones like this, although in the rebase case it\nmay well be a no-op as I can't start a rebase with a dirty index:\nhttps://github.com/alphagov/digitalmarketplace-runner/blob/f8524d9ee7465caef0571d8beb8dff4b25d10fe7/hooks/post-commit\n\nPerhaps a good question is \"what could be very catastrophic if it\nhappened during the middle of a rebase, but not such a bad thing if it\nhappened when I run 'git commit'?\"\n\n> I don't think that starting to invoke the 'post-commit' hook even more\n> without going through that thought process is an acceptable fix.\n> Since the \"right\" semantics aren't obvious here, the first step is\n> probably to collect some typical examples of post-commit hooks, like\n> the example from the message I'm replying to (thank you!).  Ideas for\n> where we can find more?\n\nThat Github search query I posted above is a pretty big haystack. I\nsuppose plenty of folks who contribute to Git have a userbase and might\nbe familiar with some workflows in use there, right?\n\n - Emily\n"},{"id":"389858","messageId":"3ea48d53940cb7e1a82a8a6eb819497c0448af6a.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:15Z","receivedAt":"2020-01-16T06:14:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCommit b00bf1c9a8dd (\"git-rebase: make --allow-empty-message the\ndefault\", 2018-06-27) made --allow-empty-message the default and thus\nturned --allow-empty-message into a no-op but did not update the\ndocumentation to reflect this.  Update the documentation now, and hide\nthe option from the normal -h output since it is not useful.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt |  7 ++++---\n builtin/rebase.c             | 12 +++++++-----\n 2 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 0c4f038dd6..c83be7ffc2 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.\n See also INCOMPATIBLE OPTIONS below.\n \n --allow-empty-message::\n-\tBy default, rebasing commits with an empty message will fail.\n-\tThis option overrides that behavior, allowing commits with empty\n-\tmessages to be rebased.\n+\tNo-op.  Rebasing commits with an empty message used to fail\n+\tand this option would override that behavior, allowing commits\n+\twith empty messages to be rebased.  Now commits with an empty\n+\tmessage do not cause rebasing to halt.\n +\n See also INCOMPATIBLE OPTIONS below.\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 8081741f8a..faa4e0d406 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n \t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n-\t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n-\t\t\t N_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n+\t\t\t   N_(\"allow commits with empty messages\"),\n+\t\t\t   PARSE_OPT_HIDDEN),\n \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_BOOL(0, \"rebase-cousins\", &opts.rebase_cousins,\n \t\t\t N_(\"keep original branch points of cousins\")),\n@@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING_LIST('x', \"exec\", &exec, N_(\"exec\"),\n \t\t\t\tN_(\"add exec lines after each commit of the \"\n \t\t\t\t   \"editable list\")),\n-\t\tOPT_BOOL(0, \"allow-empty-message\",\n-\t\t\t &options.allow_empty_message,\n-\t\t\t N_(\"allow rebasing commits with empty messages\")),\n+\t\tOPT_BOOL_F(0, \"allow-empty-message\",\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\t\tN_(\"try to rebase merges instead of skipping them\"),\n-- \ngitgitgadget\n\n"},{"id":"389859","messageId":"10fdd162a05b6e76a02f9ba9f2e00b972dba31e6.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 02/19] t3404: directly test the behavior of interest","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:16Z","receivedAt":"2020-01-16T06:14:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3404.3 is a simple test added by commit d078c3910689 (\"t3404: todo list\nwith commented-out commands only aborts\", 2018-08-10) which was designed\nto test a todo list that only contained commented-out commands.  There\nwere two problems with this test: (1) its title did not reflect the\npurpose of the test, and (2) it tested the desired behavior through a\nside-effect of other functionality instead of directly testing the\ndesired behavior discussed in the commit message.\n\nModify the test to directly test the desired behavior and update the\ntest title.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3404-rebase-interactive.sh | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ae6e55ce79..c41531f349 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -72,15 +72,16 @@ test_expect_success 'rebase --keep-empty' '\n \ttest_line_count = 6 actual\n '\n \n-test_expect_success 'rebase -i with empty HEAD' '\n+test_expect_success 'rebase -i with empty todo list' '\n \tcat >expect <<-\\EOF &&\n \terror: nothing to do\n \tEOF\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail env FAKE_LINES=\"1 exec_true\" \\\n-\t\t\tgit rebase -i HEAD^ >actual 2>&1\n+\t\ttest_must_fail env FAKE_LINES=\"#\" \\\n+\t\t\tgit rebase -i HEAD^ >output 2>&1\n \t) &&\n+\ttail -n 1 output >actual &&  # Ignore output about changing todo list\n \ttest_i18ncmp expect actual\n '\n \n-- \ngitgitgadget\n\n"},{"id":"389860","messageId":"179f82ab83e669ca8da7ce999607cae584bc18df.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:17Z","receivedAt":"2020-01-16T06:14:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nDifferent rebase backends have different treatment for commits which\nstart empty (i.e. have no changes relative to their parent), and the\n--keep-empty option was added at some point to allow adjusting behavior\nfor the interactive backend.  The handling of commits which start empty\nis actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n--allow-empty-message the default, 2018-06-27), which pointed out that\nthe behavior for various backends is often more happenstance than\ndesign.  The specific change made in that commit is actually quite\nrelevant as well and much of the logic there directly applies here.\n\nIt makes a lot of sense in 'git commit' to error out on the creation of\nempty commits, unless an override flag is provided.  However, once\nsomeone determines that there is a rare case that merits using the\nmanual override to create such a commit, it is somewhere between\nannoying and harmful to have to take extra steps to keep such\nintentional commits around.  Granted, empty commits are quite rare,\nwhich is why handling of them doesn't get considered much and folks tend\nto defer to existing (accidental) behavior and assume there was a reason\nfor it, leading them to just add flags (--keep-empty in this case) that\nallow them to override the bad defaults.  Fix the interactive backend so\nthat --keep-empty is the default, much like we did with\n--allow-empty-message.  The am backend should also be fixed to have\n--keep-empty semantics for commits that start empty, but that is not\nincluded in this patch other than a testcase documenting the failure.\n\nNote that there was one test in t3421 which appears to have been written\nexpecting --keep-empty to not be the default as correct behavior.  This\ntest was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\nempty commits\", 2013-06-06), which was part of a series focusing on\nrebase topology and which had an interesting original cover letter at\nhttps://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\nwhich noted\n    Your input especially appreciated on whether you agree with the\n    intent of the test cases.\nand then went into a long example about how one of the many tests added\nhad several questions about whether it was correct.  As such, I believe\nmost the tests in that series were about testing rebase topology with as\nmany different flags as possible and were not trying to state in general\nhow those flags should behave otherwise.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt      | 24 ++++++-----\n builtin/rebase.c                  | 28 ++++++++----\n rebase-interactive.c              |  7 +--\n rebase-interactive.h              |  2 +-\n sequencer.c                       | 20 +++------\n sequencer.h                       |  2 +-\n t/t3421-rebase-topology-linear.sh | 16 +++----\n t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++\n t/t3427-rebase-subtree.sh         | 12 +++---\n 9 files changed, 128 insertions(+), 55 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex c83be7ffc2..1d19542d79 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.\n \tunchanged as a result.\n \n --keep-empty::\n-\tKeep the commits that do not change anything from its\n-\tparents in the result.\n+\tNo-op.  Rebasing commits that started empty (had no change\n+\trelative to their parent) used to fail and this option would\n+\toverride that behavior, allowing commits with empty changes to\n+\tbe rebased.  Now commits with no changes do not cause rebasing\n+\tto halt.\n +\n-See also INCOMPATIBLE OPTIONS below.\n+See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n \n --allow-empty-message::\n \tNo-op.  Rebasing commits with an empty message used to fail\n@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.\n Empty commits\n ~~~~~~~~~~~~~\n \n-The am backend drops any \"empty\" commits, regardless of whether the\n-commit started empty (had no changes relative to its parent to\n-start with) or ended empty (all changes were already applied\n-upstream in other commits).\n+The am backend unfortunately drops intentionally empty commits, i.e.\n+commits that started empty, though these are rare in practice.  It\n+also drops commits that become empty and has no option for controlling\n+this behavior.\n \n-The interactive backend drops commits by default that\n-started empty and halts if it hits a commit that ended up empty.\n-The `--keep-empty` option exists for the interactive backend to allow\n-it to keep commits that started empty.\n+The interactive backend keeps intentionally empty commits.\n+Unfortunately, it always halts whenever it runs across a commit that\n+becomes empty, even when the rebase is not explicitly interactive.\n \n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex faa4e0d406..537b3241ce 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -77,7 +77,6 @@ struct rebase_options {\n \tconst char *action;\n \tint signoff;\n \tint allow_rerere_autoupdate;\n-\tint keep_empty;\n \tint autosquash;\n \tchar *gpg_sign_opt;\n \tint autostash;\n@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \n \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n \n-\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \treturn ret;\n }\n \n+static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_INTERACTIVE;\n+\treturn 0;\n+}\n+\n static const char * const builtin_rebase_interactive_usage[] = {\n \tN_(\"git rebase--interactive [<options>]\"),\n \tNULL\n@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n-\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\t   N_(\"allow commits with empty messages\"),\n \t\t\t   PARSE_OPT_HIDDEN),\n@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\topts->allow_rerere_autoupdate ?\n \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n-\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n-\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n-\t\t\t N_(\"preserve empty commits during rebase\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n-\tif (options.keep_empty)\n-\t\timply_interactive(&options, \"--keep-empty\");\n-\n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..7e7b4061bf 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n \treturn MISSING_COMMIT_CHECK_IGNORE;\n }\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf)\n {\n@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,\n \t\t\t\"the rebase will be aborted.\\n\\n\");\n \n \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n-\n-\tif (!keep_empty) {\n-\t\tmsg = _(\"Note that empty commits are commented out\");\n-\t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n-\t}\n }\n \n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..05354ca341 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -5,7 +5,7 @@ struct strbuf;\n struct repository;\n struct todo_list;\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf);\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/sequencer.c b/sequencer.c\nindex b9dbf1adb0..c21fc202b1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,\n \t\t       struct replay_opts *opts,\n \t\t       struct commit *commit)\n {\n-\tint index_unchanged, empty_commit;\n+\tint index_unchanged, originally_empty;\n \n \t/*\n \t * Three cases:\n@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,\n \tif (opts->keep_redundant_commits)\n \t\treturn 1;\n \n-\tempty_commit = is_original_commit_empty(commit);\n-\tif (empty_commit < 0)\n-\t\treturn empty_commit;\n-\tif (!empty_commit)\n+\toriginally_empty = is_original_commit_empty(commit);\n+\tif (originally_empty < 0)\n+\t\treturn originally_empty;\n+\tif (!originally_empty)\n \t\treturn 0;\n \telse\n \t\treturn 1;\n@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n \t\t\t\t   unsigned flags)\n {\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tif (!to_merge) {\n \t\t\t/* non-merge commit: easy case */\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tif (!keep_empty && is_empty)\n-\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n \t\t\t\t    oid_to_hex(&commit->object.oid),\n \t\t\t\t    oneline.buf);\n@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \tstruct pretty_print_context pp = {0};\n \tstruct rev_info revs;\n \tstruct commit *commit;\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n \n \twhile ((commit = get_revision(&revs))) {\n-\t\tint is_empty  = is_original_commit_empty(commit);\n+\t\tint is_empty = is_original_commit_empty(commit);\n \n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n-\t\tif (!keep_empty && is_empty)\n-\t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n \t\tstrbuf_addf(out, \"%s %s \", insn,\n \t\t\t    oid_to_hex(&commit->object.oid));\n \t\tpretty_print_commit(&pp, commit, out);\n@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \n \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n-\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n+\t\tappend_todo_help(count_commands(todo_list),\n \t\t\t\t shortrevisions, shortonto, &buf);\n \n \tres = write_message(buf.buf, buf.len, file, 0);\ndiff --git a/sequencer.h b/sequencer.h\nindex 9f9ae291e3..c165e0ff25 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-#define TODO_LIST_KEEP_EMPTY (1U << 0)\n+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 325072b0a3..57334dca7e 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '\n test_run_rebase () {\n \tresult=$1\n \tshift\n-\ttest_expect_$result \"rebase $* drops empty commit\" \"\n+\ttest_expect_$result \"rebase $* keeps begin-empty commits\" \"\n \t\treset_rebase &&\n-\t\tgit rebase $* c l &&\n-\t\ttest_cmp_rev c HEAD~2 &&\n-\t\ttest_linear_range 'd l' c..\n+\t\tgit rebase $* j l &&\n+\t\ttest_cmp_rev c HEAD~4 &&\n+\t\ttest_linear_range 'j d k l' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase failure ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase success -p\n+test_have_prereq !REBASE_P || test_run_rebase failure -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -230,7 +230,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -245,7 +245,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n test_run_rebase success --rebase-merges\n \n #       m\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nnew file mode 100755\nindex 0000000000..22d97e143b\n--- /dev/null\n+++ b/t/t3424-rebase-empty.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+\n+test_description='git rebase of commits that start or become empty'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup test repository' '\n+\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n+\ttest_write_lines A B C D E F G H I J >letters &&\n+\tgit add numbers letters &&\n+\tgit commit -m A &&\n+\n+\tgit branch upstream &&\n+\tgit branch localmods &&\n+\n+\tgit checkout upstream &&\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m B &&\n+\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C &&\n+\n+\tgit checkout localmods &&\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C2 &&\n+\n+\tgit commit --allow-empty -m D &&\n+\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m \"Five letters ought to be enough for anybody\"\n+'\n+\n+test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tgit checkout -B testing localmods &&\n+\t# rebase (--am) should not drop commits that start empty\n+\tgit rebase upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_failure 'rebase --merge with a variety of empty commits' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tgit checkout -B testing localmods &&\n+\t# rebase --merge should not halt on the commit that becomes empty\n+\tgit rebase --merge upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n+\n+test_expect_success 'rebase --interactive with a variety of empty commits' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --interactive upstream &&\n+\n+\tgit rebase --skip &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex bec48e6a1f..8dceef61cf 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n-- \ngitgitgadget\n\n"},{"id":"389861","messageId":"c9542a2abe0da284c28f1c71566db8fc5c47c1a5.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:18Z","receivedAt":"2020-01-16T06:14:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAs established in the previous commit and commit b00bf1c9a8dd\n(git-rebase: make --allow-empty-message the default, 2018-06-27), the\nbehavior for rebase with different backends in various edge or corner\ncases is often more happenstance than design.  This commit addresses\nanother such corner case: commits which \"become empty\".\n\nA careful reader may note that there are two types of commits which would\nbecome empty due to a rebase:\n\n  * [clean cherry-pick] Commits which are clean cherry-picks of upstream\n    commits, as determined by `git log --cherry-mark ...`.  Re-applying\n    these commits would result in an empty set of changes and a\n    duplicative commit message; i.e. these are commits that have\n    \"already been applied\" upstream.\n\n  * [become empty] Commits which are not empty to start, are not clean\n    cherry-picks of upstream commits, but which still become empty after\n    being rebased.  This happens e.g. when a commit has changes which\n    are a strict subset of the changes in an upstream commit, or when\n    the changes of a commit can be found spread across or among several\n    upstream commits.\n\nClearly, in both cases the changes in the commit in question are found\nupstream already, but the commit message may not be in the latter case.\n\nWhen cherry-mark can determine a commit is already upstream, then\nbecause of how cherry-mark works this means the upstream commit message\nwas about the *exact* same set of changes.  Thus, the commit messages\ncan be assumed to be fully interchangeable (and are in fact likely to be\ncompletely identical).  As such, the clean cherry-pick case represents a\ncase when there is no information to be gained by keeping the extra\ncommit around.  All rebase types have always dropped these commits, and\nno one to my knowledge has ever requested that we do otherwise.\n\nFor many of the become empty cases (and likely even most), we will also\nbe able to drop the commit without loss of information -- but this isn't\nquite always the case.  Since these commits represent cases that were\nnot clean cherry-picks, there is no upstream commit message explaining\nthe same set of changes.  Projects with good commit message hygiene will\nlikely have the explanation from our commit message contained within or\nspread among the relevant upstream commits, but not all projects run\nthat way.  As such, the commit message of the commit being rebased may\nhave reasoning that suggests additional changes that should be made to\nadapt to the new base, or it may have information that someone wants to\nadd as a note to another commit, or perhaps someone even wants to create\nan empty commit with the commit message as-is.\n\nJunio commented on the \"become-empty\" types of commits as follows[1]:\n\n    WRT a change that ends up being empty (as opposed to a change that\n    is empty from the beginning), I'd think that the current behaviour\n    is desireable one.  \"am\" based rebase is solely to transplant an\n    existing history and want to stop much less than \"interactive\" one\n    whose purpose is to polish a series before making it publishable,\n    and asking for confirmation (\"this has become empty--do you want to\n    drop it?\") is more appropriate from the workflow point of view.\n\n[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/\n\nI would simply add that his arguments for \"am\"-based rebases actually\napply to all non-explicitly-interactive rebases.  Also, since we are\nstating that different cases should have different defaults, it may be\nworth providing a flag to allow users to select which behavior they want\nfor these commits.\n\nIntroduce a new command line flag for selecting the desired behavior:\n    --empty={drop,keep,ask}\nwith the definitions:\n    drop: drop commits which become empty\n    keep: keep commits which become empty\n    ask:  provide the user a chance to interact and pick what to do with\n          commits which become empty on a case-by-case basis\n\nIn line with Junio's suggestion, if the --empty flag is not specified,\npick defaults as follows:\n    explicitly interactive: ask\n    otherwise: drop\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 27 ++++++++++++++++---\n builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++\n sequencer.c                  | 48 +++++++++++++++++++++++++--------\n sequencer.h                  |  1 +\n t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----\n t/t3427-rebase-subtree.sh    |  8 +++---\n 6 files changed, 161 insertions(+), 25 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d19542d79..551a91d764 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--empty={drop,keep,ask}::\n+\tHow to handle commits that are not empty to start and are not\n+\tclean cherry-picks of any upstream commit, but which become\n+\tempty after rebasing (because they contain a subset of already\n+\tupstream changes).  With drop (the default), commits that\n+\tbecome empty are dropped.  With keep, such commits are kept.\n+\tWith ask (implied by --interactive), the rebase will halt when\n+\tan empty commit is applied allowing you to choose whether to\n+\tdrop it, edit files more, or just commit the empty changes.\n++\n+Note that commits which start empty are kept, and commits which are\n+clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n+always dropped.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --keep-empty::\n \tNo-op.  Rebasing commits that started empty (had no change\n \trelative to their parent) used to fail and this option would\n@@ -561,6 +577,7 @@ are incompatible with the following options:\n  * --interactive\n  * --exec\n  * --keep-empty\n+ * --empty=\n  * --edit-todo\n  * --root when used in combination with --onto\n \n@@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --interactive\n  * --preserve-merges and --signoff\n  * --preserve-merges and --rebase-merges\n+ * --preserve-merges and --empty=\n  * --keep-base and --onto\n  * --keep-base and --root\n \n@@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It\n also drops commits that become empty and has no option for controlling\n this behavior.\n \n-The interactive backend keeps intentionally empty commits.\n-Unfortunately, it always halts whenever it runs across a commit that\n-becomes empty, even when the rebase is not explicitly interactive.\n+The interactive backend keeps intentionally empty commits.  Similar to\n+the am backend, by default the interactive backend drops commits that\n+become empty unless -i/--interactive is specified (in which case it\n+stops and asks the user what to do).  The interactive backend also has\n+an --empty={drop,keep,ask} option for changing the behavior of\n+handling commits that become empty.\n \n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 537b3241ce..c299869e7b 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -50,8 +50,16 @@ enum rebase_type {\n \tREBASE_PRESERVE_MERGES\n };\n \n+enum empty_type {\n+\tEMPTY_UNSPECIFIED = -1,\n+\tEMPTY_DROP,\n+\tEMPTY_KEEP,\n+\tEMPTY_ASK\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n+\tenum empty_type empty;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -91,6 +99,7 @@ struct rebase_options {\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n \treplay.allow_empty = 1;\n \treplay.allow_empty_message = opts->allow_empty_message;\n+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n \n \tBUG_ON_OPT_ARG(arg);\n \n+\t/*\n+\t * If we ever want to remap --keep-empty to --empty=keep, insert:\n+\t * \topts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n+\t */\n \topts->type = REBASE_INTERACTIVE;\n \treturn 0;\n }\n@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static enum empty_type parse_empty_value(const char *value)\n+{\n+\tif (!strcasecmp(value, \"drop\"))\n+\t\treturn EMPTY_DROP;\n+\telse if (!strcasecmp(value, \"keep\"))\n+\t\treturn EMPTY_KEEP;\n+\telse if (!strcasecmp(value, \"ask\"))\n+\t\treturn EMPTY_ASK;\n+\n+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n+}\n+\n+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\tenum empty_type value = parse_empty_value(arg);\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\toptions->empty = value;\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@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n+\t\t\t       N_(\"how to handle empty commits\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n \t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n \t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n+\tif (options.empty != EMPTY_UNSPECIFIED)\n+\t\timply_interactive(&options, \"--empty\");\n+\n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \t}\n \n+\tif (options.empty == EMPTY_UNSPECIFIED) {\n+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n+\t\t\toptions.empty = EMPTY_ASK;\n+\t\telse if (exec.nr > 0)\n+\t\t\toptions.empty = EMPTY_KEEP;\n+\t\telse\n+\t\t\toptions.empty = EMPTY_DROP;\n+\t}\n \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\ndiff --git a/sequencer.c b/sequencer.c\nindex c21fc202b1..354d0b5a38 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n \n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)\n }\n \n /*\n- * Do we run \"git commit\" with \"--allow-empty\"?\n+ * Should empty commits be allowed?  Return status:\n+ *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)\n+ *     0: Halt on empty commit\n+ *     1: Allow empty commit\n+ *     2: Drop empty commit\n  */\n static int allow_empty(struct repository *r,\n \t\t       struct replay_opts *opts,\n@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,\n \tint index_unchanged, originally_empty;\n \n \t/*\n-\t * Three cases:\n+\t * Four cases:\n \t *\n \t * (1) we do not allow empty at all and error out.\n \t *\n-\t * (2) we allow ones that were initially empty, but\n-\t * forbid the ones that become empty;\n+\t * (2) we allow ones that were initially empty, and\n+\t *     just drop the ones that become empty\n \t *\n-\t * (3) we allow both.\n+\t * (3) we allow ones that were initially empty, but\n+\t *     halt for the ones that become empty;\n+\t *\n+\t * (4) we allow both.\n \t */\n \tif (!opts->allow_empty)\n \t\treturn 0; /* let \"git commit\" barf as necessary */\n@@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,\n \toriginally_empty = is_original_commit_empty(commit);\n \tif (originally_empty < 0)\n \t\treturn originally_empty;\n-\tif (!originally_empty)\n-\t\treturn 0;\n-\telse\n+\tif (originally_empty)\n \t\treturn 1;\n+\telse if (opts->drop_redundant_commits)\n+\t\treturn 2;\n+\telse\n+\t\treturn 0;\n }\n \n static struct {\n@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,\n \tchar *author = NULL;\n \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n \tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res, unborn = 0, reword = 0, allow;\n+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n \n \tif (opts->no_commit) {\n \t\t/*\n@@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,\n \t\tgoto leave;\n \t}\n \n+\tdrop_commit = 0;\n \tallow = allow_empty(r, opts, commit);\n \tif (allow < 0) {\n \t\tres = allow;\n \t\tgoto leave;\n-\t} else if (allow)\n+\t} else if (allow == 1) {\n \t\tflags |= ALLOW_EMPTY;\n-\tif (!opts->no_commit) {\n+\t} else if (allow == 2) {\n+\t\tdrop_commit = 1;\n+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n+\t} // else allow == 0 and there's nothing special to do\n+\tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n \t\telse\n@@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n \t\t\topts->reschedule_failed_exec = 1;\n \n+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n+\t\t\topts->drop_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n+\t\t\topts->keep_redundant_commits = 1;\n+\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n@@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_gpg_sign_opt(), \"-S%s\\n\", opts->gpg_sign);\n \tif (opts->signoff)\n \t\twrite_file(rebase_path_signoff(), \"--signoff\\n\");\n+\tif (opts->drop_redundant_commits)\n+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n+\tif (opts->keep_redundant_commits)\n+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n \tif (opts->reschedule_failed_exec)\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \ndiff --git a/sequencer.h b/sequencer.h\nindex c165e0ff25..3b0ab9141f 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -39,6 +39,7 @@ struct replay_opts {\n \tint allow_rerere_auto;\n \tint allow_empty;\n \tint allow_empty_message;\n+\tint drop_redundant_commits;\n \tint keep_redundant_commits;\n \tint verbose;\n \tint quiet;\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nindex 22d97e143b..dcb4cb4751 100755\n--- a/t/t3424-rebase-empty.sh\n+++ b/t/t3424-rebase-empty.sh\n@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '\n \tgit commit -m \"Five letters ought to be enough for anybody\"\n '\n \n-test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n+test_expect_failure 'rebase (am-backend)' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tgit checkout -B testing localmods &&\n \t# rebase (--am) should not drop commits that start empty\n@@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n \ttest_cmp expect actual\n '\n \n-test_expect_failure 'rebase --merge with a variety of empty commits' '\n-\ttest_when_finished \"git rebase --abort\" &&\n+test_expect_success 'rebase --merge --empty=drop' '\n \tgit checkout -B testing localmods &&\n-\t# rebase --merge should not halt on the commit that becomes empty\n-\tgit rebase --merge upstream &&\n+\tgit rebase --merge --empty=drop upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n+\n+\tgit rebase --skip &&\n \n \ttest_write_lines D C B A >expect &&\n \tgit log --format=%s >actual &&\n@@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '\n \n GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n \n-test_expect_success 'rebase --interactive with a variety of empty commits' '\n+test_expect_success 'rebase --interactive --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=drop upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=ask' '\n \tgit checkout -B testing localmods &&\n-\ttest_must_fail git rebase --interactive upstream &&\n+\ttest_must_fail git rebase --interactive --empty=ask upstream &&\n \n \tgit rebase --skip &&\n \ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex 8dceef61cf..79e43a370b 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n \t: first pick results in no changes &&\n \tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n \tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n-- \ngitgitgadget\n\n"},{"id":"389862","messageId":"b50a1741e0b0d96c918a3f143226fd222b42baea.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 11/19] git-rebase.txt: add more details about behavioral differences of backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:25Z","receivedAt":"2020-01-16T06:14:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++---\n 1 file changed, 80 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 551a91d764..df02d76e2d 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -414,7 +414,7 @@ with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n-\tThese flag are passed to the 'git apply' program\n+\tThese flags are passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n +\n See also INCOMPATIBLE OPTIONS below.\n@@ -561,8 +561,8 @@ The following options:\n \n  * --committer-date-is-author-date\n  * --ignore-date\n- * --whitespace\n  * --ignore-whitespace\n+ * --whitespace\n  * -C\n \n are incompatible with the following options:\n@@ -613,9 +613,84 @@ handling commits that become empty.\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n-Directory rename heuristics are enabled in the merge and interactive\n-backends.  Due to the lack of accurate tree information, directory\n-rename detection is disabled in the am backend.\n+Due to the lack of accurate tree information (arising from\n+constructing fake ancestors with the limited information available in\n+patches), directory rename detection is disabled in the am backend.\n+Disabled directory rename detection means that if one side of history\n+renames a directory and the other adds new files to the old directory,\n+then the new files will be left behind in the old directory without\n+any warning at the time of rebasing that you may want to move these\n+files into the new directory.\n+\n+Directory rename detection works with the merge and interactive\n+backends to provide you warnings in such cases.\n+\n+Context\n+~~~~~~~\n+\n+The am backend works by creating a sequence of patches (by calling\n+`format-patch` internally), and then applying the patches in sequence\n+(calling `am` internally).  Patches are composed of multiple hunks,\n+each with line numbers, a context region, and the actual changes.  The\n+line numbers have to be taken with some fuzz, since the other side\n+will likely have inserted or deleted lines earlier in the file.  The\n+context region is meant to help find how to adjust the line numbers in\n+order to apply the changes to the right lines.  However, if multiple\n+areas of the code have the same surrounding lines of context, the\n+wrong one can be picked.  There are real-world cases where this has\n+caused commits to be reapplied incorrectly with no conflicts reported.\n+Setting diff.context to a larger value may prevent such types of\n+problems, but increases the chance of spurious conflicts (since it\n+will require more lines of matching context to apply).\n+\n+The interactive backend works with a full copy of each relevant file,\n+insulating it from these types of problems.\n+\n+Labelling of conflicts markers\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+When there are content conflicts, the merge machinery tries to\n+annotate each side's conflict markers with the commits where the\n+content came from.  Since the am backend drops the original\n+information about the rebased commits and their parents (and instead\n+generates new fake commits based off limited information in the\n+generated patches), those commits cannot be identified; instead it has\n+to fall back to a commit summary.  Also, when merge.conflictStyle is\n+set to diff3, the am backend will use \"constructed merge base\" to\n+label the content from the merge base, and thus provide no information\n+about the merge base commit whatsoever.\n+\n+The interactive backend works with the full commits on both sides of\n+history and thus has no such limitations.\n+\n+Hooks\n+~~~~~\n+\n+The am backend has not traditionally called the post-commit hook,\n+while the merge/interactive backend has.  However, this was by\n+accident of implementation rather than by design.  Both backends\n+should have the same behavior, though it is not clear which one is\n+correct.\n+\n+Miscellaneous differences\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+There are a few more behavioral differences that most folks would\n+probably consider inconsequential but which are mentioned for\n+completeness:\n+\n+* Reflog: The two backends will use different wording when describing\n+  the changes made in the reflog, though both will make use of the\n+  word \"rebase\".\n+\n+* Progress, informational, and error messages: The two backends\n+  provide slightly different progress and informational messages.\n+  Also, the am backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the interactive backend\n+  writes them to stderr.\n+\n+* State directories: The two backends keep their state in different\n+  directories under .git/\n \n include::merge-strategies.txt[]\n \n-- \ngitgitgadget\n\n"},{"id":"389868","messageId":"58e6e4ffb3fd10b04a64264e9e08e84bf6107ef9.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 12/19] rebase: move incompatibility checks between backend options a bit earlier","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:26Z","receivedAt":"2020-01-16T06:14:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 4b7d8fc908..55a0b2a288 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n+\tif (options.git_am_opts.argc) {\n+\t\t/* all am options except -q are compatible only with --am */\n+\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n+\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n+\t\t\t\tbreak;\n+\n+\t\tif (is_interactive(&options) && i >= 0)\n+\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t      \"interactive or merge options\"));\n+\t}\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_INTERACTIVE:\n@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (reschedule_failed_exec >= 0)\n \t\toptions.reschedule_failed_exec = reschedule_failed_exec;\n \n-\tif (options.git_am_opts.argc) {\n-\t\t/* all am options except -q are compatible only with --am */\n-\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n-\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-\t\t\t\tbreak;\n-\n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n-\t}\n-\n \tif (options.signoff) {\n \t\tif (options.type == REBASE_PRESERVE_MERGES)\n \t\t\tdie(\"cannot combine '--signoff' with \"\n-- \ngitgitgadget\n\n"},{"id":"389863","messageId":"db5e29bd818c57d88c2c317a3b3d81ff81c5c594.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 14/19] git-prompt: change the prompt for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:28Z","receivedAt":"2020-01-16T06:14:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we had different prompts for different types of rebases:\n   REBASE: for am-based rebases\n   REBASE-m: for merge-based rebases\n   REBASE-i: for interactive-based rebases\n\nIt's not clear why this distinction was necessary or helpful; when the\nprompt was added in commit e75201963f67 (\"Improve bash prompt to detect\nvarious states like an unfinished merge\", 2007-09-30), it simply added\nthese three different types.  Perhaps there was a useful purpose back\nthen, but there have been some changes:\n\n  * The merge backend was deleted after being implemented on top of the\n    interactive backend, causing the prompt for merge-based rebases to\n    change from REBASE-m to REBASE-i.\n  * The interactive backend is used for multiple different types of\n    non-interactive rebases, so the \"-i\" part of the prompt doesn't\n    really mean what it used to.\n  * Rebase backends have gained more abilities and have a great deal of\n    overlap, sometimes making it hard to distinguish them.\n  * Behavioral differences between the backends have also been ironed\n    out.\n  * We want to change the default backend from am to interactive, which\n    means people would get \"REBASE-i\" by default if we didn't change\n    the prompt, and only if they specified --am or --whitespace or -C\n    would they get the \"REBASE\" prompt.\n  * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n    run the interactive backend once it can handle everything the\n    am-backend can.\n\nFor all these reasons, make the prompt for any type of rebase just be\n\"REBASE\".\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n contrib/completion/git-prompt.sh | 6 +-----\n t/t9903-bash-prompt.sh           | 8 ++++----\n 2 files changed, 5 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\nindex 1d510cd47b..014cd7c3cf 100644\n--- a/contrib/completion/git-prompt.sh\n+++ b/contrib/completion/git-prompt.sh\n@@ -429,11 +429,7 @@ __git_ps1 ()\n \t\t__git_eread \"$g/rebase-merge/head-name\" b\n \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n \t\t__git_eread \"$g/rebase-merge/end\" total\n-\t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n-\t\t\tr=\"|REBASE-i\"\n-\t\telse\n-\t\t\tr=\"|REBASE-m\"\n-\t\tfi\n+\t\tr=\"|REBASE\"\n \telse\n \t\tif [ -d \"$g/rebase-apply\" ]; then\n \t\t\t__git_eread \"$g/rebase-apply/next\" step\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 88bc733ad6..7ca35d358d 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '\n '\n \n test_expect_success 'prompt - interactive rebase' '\n-\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n+\tprintf \" (b1|REBASE 2/3)\" >expected &&\n \twrite_script fake_editor.sh <<-\\EOF &&\n \t\techo \"exec echo\" >\"$1\"\n \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '\n '\n \n test_expect_success 'prompt - rebase merge' '\n-\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n+\tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n \ttest_must_fail git rebase --merge b1 b2 &&\n@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n \ttest_cmp expected \"$actual\"\n '\n \n-test_expect_success 'prompt - rebase' '\n+test_expect_success 'prompt - rebase am' '\n \tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase b1 b2 &&\n+\ttest_must_fail git rebase --am b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n\n"},{"id":"389864","messageId":"b6b6597eef8114b97dfcf37db9fa4d37ec944d15.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 07/19] rebase: make sure to pass along the quiet flag to the sequencer","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:21Z","receivedAt":"2020-01-16T06:14:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  | 3 ++-\n t/t3400-rebase.sh | 8 +++++++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 57875485df..e8d518ac8d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.allow_empty_message = opts->allow_empty_message;\n \treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n+\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n \t\t\t   N_(\"be quiet. implies --no-stat\"),\n-\t\t\t   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),\n+\t\t\t   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\n \t\tOPT_BIT('v', \"verbose\", &options.flags,\n \t\t\tN_(\"display a diffstat of what changed upstream\"),\n \t\t\tREBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 221b35f2df..79762b989a 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase -q is quiet' '\n+test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n \tgit rebase -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n+test_expect_success 'rebase --merge -q is quiet' '\n+\tgit checkout -B quiet topic &&\n+\tgit rebase --merge -q master >output.out 2>&1 &&\n+\ttest_must_be_empty output.out\n+'\n+\n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n \t\techo \"One\" &&\n-- \ngitgitgadget\n\n"},{"id":"389871","messageId":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v3.git.git.1577217299.gitgitgadget@gmail.com","subject":"[PATCH v4 00/19] rebase: make the default backend configurable","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:14Z","receivedAt":"2020-01-16T06:14:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is a re-roll of en/rebase-backend which has been rebased on v2.25.0 and\nupdated to remove the dependence on ra/rebase-i-more-options, and also tries\nto address feedback from Phillip, Junio, and Jonathan. This series does a\nlot of work around making the default rebase backend configurable, and\nswitches the default from the am backend to the merge/interactive one.\n\nChanges since v3:\n\n * Rebased on origin/master and updated to remove the dependence on\n   ra/rebase-i-more-options.\n * Added two new patches at the start of the series.\n * Split the old first patch into two, while modifying them based on\n   Phillip's feedback (though slightly differently than discussed on the\n   list; instead of making --keep-empty a synonym for --empty=keep, I\n   instead kept backward compatibility by making it a no-op).\n * I noted the post-commit hook in the differences between backends. Emily\n   is investigating what changes need to happen there, so I merely\n   documented the existing differences.\n * dropped '-i' from the reflog messages; now they just refer to 'rebase'\n\nChanges possibly missing from this version, for discussion:\n\n * I did not remove the --am option as suggested by Phillip, since Junio and\n   Phillip were still discussing whether it is wanted/needed.\n * I did not address the last two items Jonathan brought up as I couldn't\n   find enough information to reproduce or understand the problems.\n\nElijah Newren (19):\n  git-rebase.txt: update description of --allow-empty-message\n  t3404: directly test the behavior of interest\n  rebase (interactive-backend): make --keep-empty the default\n  rebase (interactive-backend): fix handling of commits that become\n    empty\n  t3406: simplify an already simple test\n  rebase, sequencer: remove the broken GIT_QUIET handling\n  rebase: make sure to pass along the quiet flag to the sequencer\n  rebase: fix handling of restrict_revision\n  t3432: make these tests work with either am or merge backends\n  rebase: allow more types of rebases to fast-forward\n  git-rebase.txt: add more details about behavioral differences of\n    backends\n  rebase: move incompatibility checks between backend options a bit\n    earlier\n  rebase: add an --am option\n  git-prompt: change the prompt for interactive-based rebases\n  rebase: drop '-i' from the reflog for interactive-based rebases\n  rebase tests: mark tests specific to the am-backend with --am\n  rebase tests: repeat some tests using the merge backend instead of am\n  rebase: make the backend configurable via config setting\n  rebase: change the default backend from \"am\" to \"merge\"\n\n Documentation/config/rebase.txt        |   8 ++\n Documentation/git-rebase.txt           | 150 +++++++++++++++++---\n builtin/rebase.c                       | 186 +++++++++++++++++++------\n contrib/completion/git-prompt.sh       |   6 +-\n rebase-interactive.c                   |   7 +-\n rebase-interactive.h                   |   2 +-\n sequencer.c                            |  84 ++++++-----\n sequencer.h                            |   3 +-\n t/t3400-rebase.sh                      |  36 ++++-\n t/t3401-rebase-and-am-rename.sh        |   4 +-\n t/t3404-rebase-interactive.sh          |  19 +--\n t/t3406-rebase-message.sh              |  19 ++-\n t/t3407-rebase-abort.sh                |   6 +-\n t/t3420-rebase-autostash.sh            |   2 +-\n t/t3421-rebase-topology-linear.sh      |  16 +--\n t/t3424-rebase-empty.sh                | 108 ++++++++++++++\n t/t3425-rebase-topology-merges.sh      |   8 +-\n t/t3427-rebase-subtree.sh              |  12 +-\n t/t3432-rebase-fast-forward.sh         |  54 ++++---\n t/t5407-post-rewrite-hook.sh           |  12 +-\n t/t5520-pull.sh                        |  27 +++-\n t/t6047-diff3-conflict-markers.sh      |  13 +-\n t/t7512-status-help.sh                 |  12 +-\n t/t9106-git-svn-commit-diff-clobber.sh |   3 +-\n t/t9903-bash-prompt.sh                 |   8 +-\n 25 files changed, 595 insertions(+), 210 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\n\nbase-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v4\nPull-Request: https://github.com/git/git/pull/679\n\nRange-diff vs v3:\n\n  -:  ---------- >  1:  3ea48d5394 git-rebase.txt: update description of --allow-empty-message\n  -:  ---------- >  2:  10fdd162a0 t3404: directly test the behavior of interest\n  1:  1c2b77e94d !  3:  179f82ab83 rebase: extend the options for handling of empty commits\n     @@ -1,49 +1,46 @@\n      Author: Elijah Newren <newren@gmail.com>\n      \n     -    rebase: extend the options for handling of empty commits\n     +    rebase (interactive-backend): make --keep-empty the default\n      \n     -    Extend the interactive machinery with the ability to handle the full\n     -    spread of options for how to handle commits that either start or become\n     -    empty (by \"become empty\" I mean the changes in a commit are a subset of\n     -    changes that exist upstream, so the net effect of applying the commit is\n     -    no changes).  Introduce a new command line flag for selecting the\n     -    desired behavior:\n     -        --empty={drop,keep,ask}\n     -    with the definitions:\n     -        drop: drop empty commits\n     -        keep: keep empty commits\n     -        ask:  provide the user a chance to interact and pick what to do with\n     -              empty commits on a case-by-case basis\n     +    Different rebase backends have different treatment for commits which\n     +    start empty (i.e. have no changes relative to their parent), and the\n     +    --keep-empty option was added at some point to allow adjusting behavior\n     +    for the interactive backend.  The handling of commits which start empty\n     +    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n     +    --allow-empty-message the default, 2018-06-27), which pointed out that\n     +    the behavior for various backends is often more happenstance than\n     +    design.  The specific change made in that commit is actually quite\n     +    relevant as well and much of the logic there directly applies here.\n      \n     -    Note that traditionally, am-based rebases have always dropped commits\n     -    that either started or became empty, while interactive-based rebases\n     -    have defaulted to ask (and provided an option to keep commits that\n     -    started empty).  This difference made sense since users of an am-based\n     -    rebase just wanted to quickly batch apply a sequence of commits, while\n     -    users editing a todo list will likely want the chance to interact and\n     -    handle unusual cases on a case-by-case basis.  However, not all rebases\n     -    using the interactive machinery are explicitly interactive anymore.  In\n     -    particular --merge was always meant to behave more like --am: just\n     -    rebase a batch of commits without popping up a todo list.\n     +    It makes a lot of sense in 'git commit' to error out on the creation of\n     +    empty commits, unless an override flag is provided.  However, once\n     +    someone determines that there is a rare case that merits using the\n     +    manual override to create such a commit, it is somewhere between\n     +    annoying and harmful to have to take extra steps to keep such\n     +    intentional commits around.  Granted, empty commits are quite rare,\n     +    which is why handling of them doesn't get considered much and folks tend\n     +    to defer to existing (accidental) behavior and assume there was a reason\n     +    for it, leading them to just add flags (--keep-empty in this case) that\n     +    allow them to override the bad defaults.  Fix the interactive backend so\n     +    that --keep-empty is the default, much like we did with\n     +    --allow-empty-message.  The am backend should also be fixed to have\n     +    --keep-empty semantics for commits that start empty, but that is not\n     +    included in this patch other than a testcase documenting the failure.\n      \n     -    If the --empty flag is not specified, pick defaults as follows:\n     -        explicitly interactive: ask\n     -        --exec: keep (exec is about checking existing commits, and often\n     -                      used without actually changing the base.  Thus the\n     -                      expectation is that the user doesn't necessarily want\n     -                      anything to change; they just want to test).\n     -        otherwise: drop\n     -\n     -    Also, this commit makes --keep-empty just imply --empty=keep, and hides\n     -    it from help so that we aren't confusing users with different ways to do\n     -    the same thing.  (I could have added a --drop-empty flag, but then that\n     -    invites users to specify both --keep-empty and --drop-empty and we have\n     -    to add sanity checking around that; it seems cleaner to have a single\n     -    multi-valued option.)  This actually fixes --keep-empty too; previously,\n     -    it only meant to sometimes keep empty commits, in particular commits\n     -    which started empty would be kept.  But it would still error out and ask\n     -    the user what to do with commits that became empty.  Now it keeps empty\n     -    commits, as instructed.\n     +    Note that there was one test in t3421 which appears to have been written\n     +    expecting --keep-empty to not be the default as correct behavior.  This\n     +    test was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\n     +    empty commits\", 2013-06-06), which was part of a series focusing on\n     +    rebase topology and which had an interesting original cover letter at\n     +    https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\n     +    which noted\n     +        Your input especially appreciated on whether you agree with the\n     +        intent of the test cases.\n     +    and then went into a long example about how one of the many tests added\n     +    had several questions about whether it was correct.  As such, I believe\n     +    most the tests in that series were about testing rebase topology with as\n     +    many different flags as possible and were not trying to state in general\n     +    how those flags should behave otherwise.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ -51,126 +48,62 @@\n       --- a/Documentation/git-rebase.txt\n       +++ b/Documentation/git-rebase.txt\n      @@\n     - \toriginal branch. The index and working tree are also left\n       \tunchanged as a result.\n       \n     -+--empty={drop,keep,ask}::\n     -+\tHow to handle commits that become empty (because they contain a\n     -+\tsubset of already upstream changes) or start empty.  With drop\n     -+\t(the default), commits that start or become empty are dropped.\n     -+\tWith keep (implied by --exec), such commits are kept.  With ask\n     -+\t(implied by --interactive), the rebase will halt when an empty\n     -+\tcommit is applied allowing you to choose whether to drop it or\n     -+\tcommit it.  Also with ask, if the rebase is interactive then\n     -+\tcommits which start empty will be commented out in the todo\n     -+\taction list (giving you a chance to uncomment).\n     -++\n     -+Note that this has no effect on commits which are already upstream (as\n     -+can be checked via `git log --cherry-mark ...`), which are always\n     -+dropped by rebase.\n     -++\n     -+See also INCOMPATIBLE OPTIONS below.\n     -+\n       --keep-empty::\n      -\tKeep the commits that do not change anything from its\n      -\tparents in the result.\n     -+\tDeprecated alias for what is now known as --empty=keep.\n     ++\tNo-op.  Rebasing commits that started empty (had no change\n     ++\trelative to their parent) used to fail and this option would\n     ++\toverride that behavior, allowing commits with empty changes to\n     ++\tbe rebased.  Now commits with no changes do not cause rebasing\n     ++\tto halt.\n       +\n     - See also INCOMPATIBLE OPTIONS below.\n     - \n     -@@\n     -  * --interactive\n     -  * --exec\n     -  * --keep-empty\n     -+ * --empty=\n     -  * --edit-todo\n     -  * --root when used in combination with --onto\n     - \n     -@@\n     -  * --preserve-merges and --ignore-whitespace\n     -  * --preserve-merges and --committer-date-is-author-date\n     -  * --preserve-merges and --ignore-date\n     -+ * --preserve-merges and --empty=\n     -  * --keep-base and --onto\n     -  * --keep-base and --root\n     +-See also INCOMPATIBLE OPTIONS below.\n     ++See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n       \n     + --allow-empty-message::\n     + \tNo-op.  Rebasing commits with an empty message used to fail\n      @@\n     + Empty commits\n     + ~~~~~~~~~~~~~\n       \n     - There are some subtle differences how the backends behave.\n     - \n     --Empty commits\n     --~~~~~~~~~~~~~\n     --\n      -The am backend drops any \"empty\" commits, regardless of whether the\n      -commit started empty (had no changes relative to its parent to\n      -start with) or ended empty (all changes were already applied\n      -upstream in other commits).\n     --\n     ++The am backend unfortunately drops intentionally empty commits, i.e.\n     ++commits that started empty, though these are rare in practice.  It\n     ++also drops commits that become empty and has no option for controlling\n     ++this behavior.\n     + \n      -The interactive backend drops commits by default that\n      -started empty and halts if it hits a commit that ended up empty.\n      -The `--keep-empty` option exists for the interactive backend to allow\n      -it to keep commits that started empty.\n     --\n     ++The interactive backend keeps intentionally empty commits.\n     ++Unfortunately, it always halts whenever it runs across a commit that\n     ++becomes empty, even when the rebase is not explicitly interactive.\n     + \n       Directory rename detection\n       ~~~~~~~~~~~~~~~~~~~~~~~~~~\n     - \n      \n       diff --git a/builtin/rebase.c b/builtin/rebase.c\n       --- a/builtin/rebase.c\n       +++ b/builtin/rebase.c\n     -@@\n     - \tREBASE_PRESERVE_MERGES\n     - };\n     - \n     -+enum empty_type {\n     -+\tEMPTY_UNSPECIFIED = -1,\n     -+\tEMPTY_DROP,\n     -+\tEMPTY_KEEP,\n     -+\tEMPTY_ASK\n     -+};\n     -+\n     - struct rebase_options {\n     - \tenum rebase_type type;\n     -+\tenum empty_type empty;\n     - \tconst char *state_dir;\n     - \tstruct commit *upstream;\n     - \tconst char *upstream_name;\n      @@\n       \tconst char *action;\n       \tint signoff;\n       \tint allow_rerere_autoupdate;\n      -\tint keep_empty;\n       \tint autosquash;\n     - \tint ignore_whitespace;\n       \tchar *gpg_sign_opt;\n     -@@\n     - \n     - #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n     - \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n     -+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n     - \t\t.flags = REBASE_NO_QUIET, \t\t\\\n     - \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n     - \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n     -@@\n     - \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n     - \treplay.allow_empty = 1;\n     - \treplay.allow_empty_message = opts->allow_empty_message;\n     -+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n     -+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n     -+\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n     -+\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n     - \treplay.verbose = opts->flags & REBASE_VERBOSE;\n     - \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n     - \treplay.committer_date_is_author_date =\n     + \tint autostash;\n      @@\n       \n       \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n       \n      -\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n     -+\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n     -+\tflags |= (opts->empty == EMPTY_ASK &&\n     -+\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n     -+\t\t\tTODO_LIST_ASK_EMPTY : 0;\n       \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n       \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n       \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n     @@ -183,10 +116,8 @@\n      +{\n      +\tstruct rebase_options *opts = opt->value;\n      +\n     -+\tBUG_ON_OPT_NEG(unset);\n      +\tBUG_ON_OPT_ARG(arg);\n      +\n     -+\topts->empty = EMPTY_KEEP;\n      +\topts->type = REBASE_INTERACTIVE;\n      +\treturn 0;\n      +}\n     @@ -201,62 +132,28 @@\n      -\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n      +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n      +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n     -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n     ++\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n      +\t\t\tparse_opt_keep_empty },\n     - \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n     - \t\t\t N_(\"allow commits with empty messages\")),\n     - \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n     + \t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n     + \t\t\t   N_(\"allow commits with empty messages\"),\n     + \t\t\t   PARSE_OPT_HIDDEN),\n      @@\n       \t\topts->allow_rerere_autoupdate ?\n       \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n       \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n      -\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n     -+\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n       \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n       \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n       \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n     -@@\n     - \treturn 0;\n     - }\n     - \n     -+static enum empty_type parse_empty_value(const char *value)\n     -+{\n     -+\tif (!strcasecmp(value, \"drop\"))\n     -+\t\treturn EMPTY_DROP;\n     -+\telse if (!strcasecmp(value, \"keep\"))\n     -+\t\treturn EMPTY_KEEP;\n     -+\telse if (!strcasecmp(value, \"ask\"))\n     -+\t\treturn EMPTY_ASK;\n     -+\n     -+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n     -+}\n     -+\n     -+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n     -+{\n     -+\tstruct rebase_options *options = opt->value;\n     -+\tenum empty_type value = parse_empty_value(arg);\n     -+\n     -+\tBUG_ON_OPT_NEG(unset);\n     -+\n     -+\toptions->empty = value;\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      @@\n       \t\t\t\t \"ignoring them\"),\n       \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n       \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n      -\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n      -\t\t\t N_(\"preserve empty commits during rebase\")),\n     -+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n     -+\t\t\t       N_(\"how to handle empty commits\"),\n     -+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n      +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n      +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n     -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n     ++\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n      +\t\t\tparse_opt_keep_empty },\n       \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n       \t\t\t N_(\"move commits that begin with \"\n     @@ -267,26 +164,10 @@\n       \n      -\tif (options.keep_empty)\n      -\t\timply_interactive(&options, \"--keep-empty\");\n     -+\tif (options.empty != EMPTY_UNSPECIFIED)\n     -+\t\timply_interactive(&options, \"--empty\");\n     - \n     +-\n       \tif (gpg_sign) {\n       \t\tfree(options.gpg_sign_opt);\n     -@@\n     - \t\tbreak;\n     - \t}\n     - \n     -+\tif (options.empty == EMPTY_UNSPECIFIED) {\n     -+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n     -+\t\t\toptions.empty = EMPTY_ASK;\n     -+\t\telse if (exec.nr > 0)\n     -+\t\t\toptions.empty = EMPTY_KEEP;\n     -+\t\telse\n     -+\t\t\toptions.empty = EMPTY_DROP;\n     -+\t}\n     - \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n     - \t\tdie(_(\"--reschedule-failed-exec requires \"\n     - \t\t      \"--exec or --interactive\"));\n     + \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n      \n       diff --git a/rebase-interactive.c b/rebase-interactive.c\n       --- a/rebase-interactive.c\n     @@ -296,19 +177,22 @@\n       }\n       \n      -void append_todo_help(unsigned keep_empty, int command_count,\n     -+void append_todo_help(unsigned no_ask_empty, int command_count,\n     ++void append_todo_help(int command_count,\n       \t\t      const char *shortrevisions, const char *shortonto,\n       \t\t      struct strbuf *buf)\n       {\n      @@\n     + \t\t\t\"the rebase will be aborted.\\n\\n\");\n       \n       \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n     - \n     +-\n      -\tif (!keep_empty) {\n     -+\tif (!no_ask_empty) {\n     - \t\tmsg = _(\"Note that empty commits are commented out\");\n     - \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n     - \t}\n     +-\t\tmsg = _(\"Note that empty commits are commented out\");\n     +-\t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n     +-\t}\n     + }\n     + \n     + int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n      \n       diff --git a/rebase-interactive.h b/rebase-interactive.h\n       --- a/rebase-interactive.h\n     @@ -318,7 +202,7 @@\n       struct todo_list;\n       \n      -void append_todo_help(unsigned keep_empty, int command_count,\n     -+void append_todo_help(unsigned no_ask_empty, int command_count,\n     ++void append_todo_help(int command_count,\n       \t\t      const char *shortrevisions, const char *shortonto,\n       \t\t      struct strbuf *buf);\n       int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n     @@ -327,157 +211,51 @@\n       --- a/sequencer.c\n       +++ b/sequencer.c\n      @@\n     - static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n     - static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n     - static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n     -+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n     -+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n     -+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n     - \n     - static int git_sequencer_config(const char *k, const char *v, void *cb)\n     + \t\t       struct replay_opts *opts,\n     + \t\t       struct commit *commit)\n       {\n     +-\tint index_unchanged, empty_commit;\n     ++\tint index_unchanged, originally_empty;\n     + \n     + \t/*\n     + \t * Three cases:\n      @@\n     - \tempty_commit = is_original_commit_empty(commit);\n     - \tif (empty_commit < 0)\n     - \t\treturn empty_commit;\n     + \tif (opts->keep_redundant_commits)\n     + \t\treturn 1;\n     + \n     +-\tempty_commit = is_original_commit_empty(commit);\n     +-\tif (empty_commit < 0)\n     +-\t\treturn empty_commit;\n      -\tif (!empty_commit)\n     -+\tif (!empty_commit || opts->ask_on_initially_empty)\n     ++\toriginally_empty = is_original_commit_empty(commit);\n     ++\tif (originally_empty < 0)\n     ++\t\treturn originally_empty;\n     ++\tif (!originally_empty)\n       \t\treturn 0;\n       \telse\n       \t\treturn 1;\n     -@@\n     - \tchar *author = NULL;\n     - \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n     - \tstruct strbuf msgbuf = STRBUF_INIT;\n     --\tint res, unborn = 0, reword = 0, allow;\n     -+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n     - \n     - \tif (opts->no_commit) {\n     - \t\t/*\n     -@@\n     - \t\tgoto leave;\n     - \t}\n     - \n     --\tallow = allow_empty(r, opts, commit);\n     --\tif (allow < 0) {\n     --\t\tres = allow;\n     --\t\tgoto leave;\n     --\t} else if (allow)\n     --\t\tflags |= ALLOW_EMPTY;\n     --\tif (!opts->no_commit) {\n     -+\tdrop_commit = 0;\n     -+\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n     -+\t\tdrop_commit = 1;\n     -+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n     -+\t} else {\n     -+\t\tallow = allow_empty(r, opts, commit);\n     -+\t\tif (allow < 0) {\n     -+\t\t\tres = allow;\n     -+\t\t\tgoto leave;\n     -+\t\t} else if (allow) {\n     -+\t\t\tflags |= ALLOW_EMPTY;\n     -+\t\t}\n     -+\t}\n     -+\tif (!opts->no_commit && !drop_commit) {\n     - \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n     - \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n     - \t\telse\n     -@@\n     - \telse if (!strcmp(key, \"options.allow-empty-message\"))\n     - \t\topts->allow_empty_message =\n     - \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     -+\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n     -+\t\topts->drop_redundant_commits =\n     -+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     - \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n     - \t\topts->keep_redundant_commits =\n     - \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     -+\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n     -+\t\topts->ask_on_initially_empty =\n     -+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     - \telse if (!strcmp(key, \"options.signoff\"))\n     - \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n     - \telse if (!strcmp(key, \"options.record-origin\"))\n     -@@\n     - \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n     - \t\t\topts->reschedule_failed_exec = 1;\n     - \n     -+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n     -+\t\t\topts->drop_redundant_commits = 1;\n     -+\n     -+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n     -+\t\t\topts->keep_redundant_commits = 1;\n     -+\n     -+\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n     -+\t\t\topts->ask_on_initially_empty = 1;\n     -+\n     - \t\tread_strategy_opts(opts, &buf);\n     - \t\tstrbuf_release(&buf);\n     - \n     -@@\n     - \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n     - \tif (opts->ignore_date)\n     - \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n     -+\tif (opts->drop_redundant_commits)\n     -+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n     -+\tif (opts->keep_redundant_commits)\n     -+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n     -+\tif (opts->ask_on_initially_empty)\n     -+\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n     - \tif (opts->reschedule_failed_exec)\n     - \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n     - \n     -@@\n     - \tif (opts->allow_empty_message)\n     - \t\tres |= git_config_set_in_file_gently(opts_file,\n     - \t\t\t\t\"options.allow-empty-message\", \"true\");\n     -+\tif (opts->drop_redundant_commits)\n     -+\t\tres |= git_config_set_in_file_gently(opts_file,\n     -+\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n     - \tif (opts->keep_redundant_commits)\n     - \t\tres |= git_config_set_in_file_gently(opts_file,\n     - \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n     -+\tif (opts->ask_on_initially_empty)\n     -+\t\tres |= git_config_set_in_file_gently(opts_file,\n     -+\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n     - \tif (opts->signoff)\n     - \t\tres |= git_config_set_in_file_gently(opts_file,\n     - \t\t\t\t\t\"options.signoff\", \"true\");\n      @@\n       \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n       \t\t\t\t   unsigned flags)\n       {\n      -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n     -+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n     -+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n       \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n       \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n       \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n     -@@\n     - \t\tis_empty = is_original_commit_empty(commit);\n     - \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n     - \t\t\tcontinue;\n     -+\t\tif (is_empty && drop_empty)\n     -+\t\t\tcontinue;\n     - \n     - \t\tstrbuf_reset(&oneline);\n     - \t\tpretty_print_commit(pp, commit, &oneline);\n      @@\n       \t\tif (!to_merge) {\n       \t\t\t/* non-merge commit: easy case */\n       \t\t\tstrbuf_reset(&buf);\n      -\t\t\tif (!keep_empty && is_empty)\n     -+\t\t\tif (is_empty && ask_empty)\n     - \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n     +-\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n       \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n       \t\t\t\t    oid_to_hex(&commit->object.oid),\n     + \t\t\t\t    oneline.buf);\n      @@\n       \tstruct pretty_print_context pp = {0};\n       \tstruct rev_info revs;\n       \tstruct commit *commit;\n      -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n     -+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n     -+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n       \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n       \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n       \n     @@ -491,19 +269,16 @@\n       \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n       \t\t\tcontinue;\n      -\t\tif (!keep_empty && is_empty)\n     -+\t\tif (is_empty && drop_empty)\n     -+\t\t\tcontinue;\n     -+\t\tif (is_empty && ask_empty)\n     - \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n     +-\t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n       \t\tstrbuf_addf(out, \"%s %s \", insn,\n       \t\t\t    oid_to_hex(&commit->object.oid));\n     + \t\tpretty_print_commit(&pp, commit, out);\n      @@\n       \n       \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n       \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n      -\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n     -+\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n     -+\t\t\t\t count_commands(todo_list),\n     ++\t\tappend_todo_help(count_commands(todo_list),\n       \t\t\t\t shortrevisions, shortonto, &buf);\n       \n       \tres = write_message(buf.buf, buf.len, file, 0);\n     @@ -511,16 +286,6 @@\n       diff --git a/sequencer.h b/sequencer.h\n       --- a/sequencer.h\n       +++ b/sequencer.h\n     -@@\n     - \tint allow_rerere_auto;\n     - \tint allow_empty;\n     - \tint allow_empty_message;\n     -+\tint drop_redundant_commits;\n     - \tint keep_redundant_commits;\n     -+\tint ask_on_initially_empty;\n     - \tint verbose;\n     - \tint quiet;\n     - \tint reschedule_failed_exec;\n      @@\n       int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n       int sequencer_remove_state(struct replay_opts *opts);\n     @@ -530,19 +295,34 @@\n       #define TODO_LIST_SHORTEN_IDS (1U << 1)\n       #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n       #define TODO_LIST_REBASE_MERGES (1U << 3)\n     -@@\n     -  * `--onto`, we do not want to re-generate the root commits.\n     -  */\n     - #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n     -+#define TODO_LIST_DROP_EMPTY (1U << 7)\n     -+#define TODO_LIST_ASK_EMPTY (1U << 8)\n     - \n     - \n     - int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n      \n       diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n       --- a/t/t3421-rebase-topology-linear.sh\n       +++ b/t/t3421-rebase-topology-linear.sh\n     +@@\n     + test_run_rebase () {\n     + \tresult=$1\n     + \tshift\n     +-\ttest_expect_$result \"rebase $* drops empty commit\" \"\n     ++\ttest_expect_$result \"rebase $* keeps begin-empty commits\" \"\n     + \t\treset_rebase &&\n     +-\t\tgit rebase $* c l &&\n     +-\t\ttest_cmp_rev c HEAD~2 &&\n     +-\t\ttest_linear_range 'd l' c..\n     ++\t\tgit rebase $* j l &&\n     ++\t\ttest_cmp_rev c HEAD~4 &&\n     ++\t\ttest_linear_range 'j d k l' c..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase failure ''\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     +-test_have_prereq !REBASE_P || test_run_rebase success -p\n     ++test_have_prereq !REBASE_P || test_run_rebase failure -p\n     + \n     + test_run_rebase () {\n     + \tresult=$1\n      @@\n       test_run_rebase success ''\n       test_run_rebase success -m\n     @@ -603,31 +383,22 @@\n      +\tgit commit -m \"Five letters ought to be enough for anybody\"\n      +'\n      +\n     -+test_expect_success 'rebase --merge --empty=drop' '\n     ++test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n     ++\ttest_when_finished \"git rebase --abort\" &&\n      +\tgit checkout -B testing localmods &&\n     -+\tgit rebase --merge --empty=drop upstream &&\n     -+\n     -+\ttest_write_lines C B A >expect &&\n     -+\tgit log --format=%s >actual &&\n     -+\ttest_cmp expect actual\n     -+'\n     ++\t# rebase (--am) should not drop commits that start empty\n     ++\tgit rebase upstream &&\n      +\n     -+test_expect_success 'rebase --merge --empty=keep' '\n     -+\tgit checkout -B testing localmods &&\n     -+\tgit rebase --merge --empty=keep upstream &&\n     -+\n     -+\ttest_write_lines D C2 C B A >expect &&\n     ++\ttest_write_lines D C B A >expect &&\n      +\tgit log --format=%s >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'rebase --merge --empty=ask' '\n     ++test_expect_failure 'rebase --merge with a variety of empty commits' '\n     ++\ttest_when_finished \"git rebase --abort\" &&\n      +\tgit checkout -B testing localmods &&\n     -+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n     -+\n     -+\ttest_must_fail git rebase --skip &&\n     -+\tgit commit --allow-empty &&\n     -+\tgit rebase --continue &&\n     ++\t# rebase --merge should not halt on the commit that becomes empty\n     ++\tgit rebase --merge upstream &&\n      +\n      +\ttest_write_lines D C B A >expect &&\n      +\tgit log --format=%s >actual &&\n     @@ -636,25 +407,17 @@\n      +\n      +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n      +\n     -+test_expect_success 'rebase --interactive --empty=drop' '\n     ++test_expect_success 'rebase --interactive with a variety of empty commits' '\n      +\tgit checkout -B testing localmods &&\n     -+\tgit rebase --interactive --empty=drop upstream &&\n     ++\ttest_must_fail git rebase --interactive upstream &&\n      +\n     -+\ttest_write_lines C B A >expect &&\n     -+\tgit log --format=%s >actual &&\n     -+\ttest_cmp expect actual\n     -+'\n     -+\n     -+test_expect_success 'rebase --interactive --empty=keep' '\n     -+\tgit checkout -B testing localmods &&\n     -+\tgit rebase --interactive --empty=keep upstream &&\n     ++\tgit rebase --skip &&\n      +\n     -+\ttest_write_lines D C2 C B A >expect &&\n     ++\ttest_write_lines D C B A >expect &&\n      +\tgit log --format=%s >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+\n      +test_done\n      \n       diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n     @@ -665,32 +428,28 @@\n       '\n       \n      -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n     -+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n     ++test_expect_success 'Rebase -Xsubtree --onto commit' '\n       \treset_rebase &&\n       \tgit checkout -b rebase-onto to-rebase &&\n      -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n     -+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n     ++\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n       \t: first pick results in no changes &&\n      -\tgit rebase --continue &&\n     -+\ttest_must_fail git rebase --skip &&\n     -+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n     -+\tgit commit --allow-empty &&\n     ++\tgit rebase --skip &&\n       \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n       \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n       \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n       '\n       \n      -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n     -+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n     ++test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n       \treset_rebase &&\n       \tgit checkout -b rebase-merges-onto to-rebase &&\n      -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n     -+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n     ++\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n       \t: first pick results in no changes &&\n      -\tgit rebase --continue &&\n     -+\ttest_must_fail git rebase --skip &&\n     -+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n     -+\tgit commit --allow-empty &&\n     ++\tgit rebase --skip &&\n       \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n       \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n       \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n  -:  ---------- >  4:  c9542a2abe rebase (interactive-backend): fix handling of commits that become empty\n  2:  bd3c5ec155 =  5:  9f66229d5c t3406: simplify an already simple test\n  3:  49388b79fd =  6:  8d731fa39c rebase, sequencer: remove the broken GIT_QUIET handling\n  4:  478479358f !  7:  b6b6597eef rebase: make sure to pass along the quiet flag to the sequencer\n     @@ -8,13 +8,13 @@\n       --- a/builtin/rebase.c\n       +++ b/builtin/rebase.c\n      @@\n     + \treplay.allow_empty_message = opts->allow_empty_message;\n     + \treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n       \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n     - \treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n     - \t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n      +\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n       \treplay.verbose = opts->flags & REBASE_VERBOSE;\n       \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n     - \treplay.committer_date_is_author_date =\n     + \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n      @@\n       \t\t\t N_(\"allow pre-rebase hook to run\")),\n       \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n  5:  ee26f5a161 =  8:  0acefa988b rebase: fix handling of restrict_revision\n  6:  34a69def33 =  9:  8c5b5b5133 t3432: make these tests work with either am or merge backends\n  7:  f2c92853b4 ! 10:  b8c087d6fb rebase: allow more types of rebases to fast-forward\n     @@ -35,8 +35,8 @@\n       \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n       \t\t\t   N_(\"revision\"),\n      @@\n     - \t    options.ignore_date)\n     - \t\toptions.flags |= REBASE_FORCE;\n     + \t\t    state_dir_base, cmd_live_rebase, buf.buf);\n     + \t}\n       \n      +\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n      +\t    (action != ACTION_NONE) ||\n     @@ -47,7 +47,9 @@\n      +\n       \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n       \t\tconst char *option = options.git_am_opts.argv[i], *p;\n     - \t\tif (!strcmp(option, \"--whitespace=fix\") ||\n     + \t\tif (!strcmp(option, \"--committer-date-is-author-date\") ||\n     + \t\t    !strcmp(option, \"--ignore-date\") ||\n     + \t\t    !strcmp(option, \"--whitespace=fix\") ||\n       \t\t    !strcmp(option, \"--whitespace=strip\"))\n      -\t\t\toptions.flags |= REBASE_FORCE;\n      +\t\t\tallow_preemptive_ff = 0;\n  8:  b307340f7c ! 11:  b50a1741e0 git-rebase.txt: add more details about behavioral differences of backends\n     @@ -8,23 +8,24 @@\n       --- a/Documentation/git-rebase.txt\n       +++ b/Documentation/git-rebase.txt\n      @@\n     - with `--keep-base` in order to drop those commits from your branch.\n       \n       --ignore-whitespace::\n     --\tBehaves differently depending on which backend is selected.\n     --+\n     --'am' backend: When applying a patch, ignore changes in whitespace in\n     --context lines if necessary.\n     --+\n     --'interactive' backend: Treat lines with only whitespace changes as\n     --unchanged for the sake of a three-way merge.\n     -+\tIgnore whitespace-only changes in the commits being rebased,\n     -+\twhich may avoid \"unnecessary\" conflicts.  (Both backends\n     -+\tcurrently have differing edgecase bugs with this option; see\n     -+\tBEHAVIORAL DIFFERENCES.)\n     - \n       --whitespace=<option>::\n     - \tThis flag is passed to the 'git apply' program\n     +-\tThese flag are passed to the 'git apply' program\n     ++\tThese flags are passed to the 'git apply' program\n     + \t(see linkgit:git-apply[1]) that applies the patch.\n     + +\n     + See also INCOMPATIBLE OPTIONS below.\n     +@@\n     + \n     +  * --committer-date-is-author-date\n     +  * --ignore-date\n     +- * --whitespace\n     +  * --ignore-whitespace\n     ++ * --whitespace\n     +  * -C\n     + \n     + are incompatible with the following options:\n      @@\n       Directory rename detection\n       ~~~~~~~~~~~~~~~~~~~~~~~~~~\n     @@ -82,24 +83,14 @@\n      +The interactive backend works with the full commits on both sides of\n      +history and thus has no such limitations.\n      +\n     -+--ignore-whitespace\n     -+~~~~~~~~~~~~~~~~~~~\n     ++Hooks\n     ++~~~~~\n      +\n     -+The --ignore-whitespace option is supposed to ignore whitespace-only\n     -+changes if it allows the code to merge cleanly.  Unfortunately, the\n     -+different backends implement this differently, and both have different\n     -+edge case bugs.\n     -++\n     -+'am' backend: When applying a patch, ignore changes in whitespace in\n     -+context lines if necessary.  (Which implies that if the whitespace\n     -+change was not in the context lines but on a line with a real change,\n     -+then the rebase will still fail with \"unnecessary\" content conflicts.)\n     -++\n     -+'interactive' backend: Treat lines with only whitespace changes as\n     -+unchanged for the sake of a three-way merge.  This means that if one\n     -+side made no changes and the commits being rebased had whitespace-only\n     -+changes, those whitespaces fixups will be discarded despite the fact\n     -+that they present no content conflict.\n     ++The am backend has not traditionally called the post-commit hook,\n     ++while the merge/interactive backend has.  However, this was by\n     ++accident of implementation rather than by design.  Both backends\n     ++should have the same behavior, though it is not clear which one is\n     ++correct.\n      +\n      +Miscellaneous differences\n      +~~~~~~~~~~~~~~~~~~~~~~~~~\n     @@ -123,19 +114,3 @@\n       \n       include::merge-strategies.txt[]\n       \n     -\n     - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\n     - --- a/t/t3433-rebase-options-compatibility.sh\n     - +++ b/t/t3433-rebase-options-compatibility.sh\n     -@@\n     - GIT_AUTHOR_DATE=\"1999-04-02T08:03:20+05:30\"\n     - export GIT_AUTHOR_DATE\n     - \n     --# This is a special case in which both am and interactive backends\n     --# provide the same output. It was done intentionally because\n     --# both the backends fall short of optimal behaviour.\n     -+# This is a common case in which both am and interactive backends\n     -+# provide the same output with --ignore-whitespace.\n     - test_expect_success 'setup' '\n     - \tgit checkout -b topic &&\n     - \tq_to_tab >file <<-\\EOF &&\n  9:  7c3f2e07f3 = 12:  58e6e4ffb3 rebase: move incompatibility checks between backend options a bit earlier\n 10:  1df11f0b51 ! 13:  5478c730ac rebase: add an --am option\n     @@ -24,8 +24,8 @@\n      +See also INCOMPATIBLE OPTIONS below.\n      +\n       --empty={drop,keep,ask}::\n     - \tHow to handle commits that become empty (because they contain a\n     - \tsubset of already upstream changes) or start empty.  With drop\n     + \tHow to handle commits that are not empty to start and are not\n     + \tclean cherry-picks of any upstream commit, but which become\n      @@\n       \tEnsure at least <n> lines of surrounding context match before\n       \tand after each change.  When fewer lines of surrounding\n     @@ -37,7 +37,7 @@\n       \n      @@\n       --whitespace=<option>::\n     - \tThis flag is passed to the 'git apply' program\n     + \tThese flags are passed to the 'git apply' program\n       \t(see linkgit:git-apply[1]) that applies the patch.\n      +\tImplies --am.\n       +\n     @@ -48,9 +48,9 @@\n       The following options:\n       \n      + * --am\n     -  * --whitespace\n     -  * -C\n     - \n     +  * --committer-date-is-author-date\n     +  * --ignore-date\n     +  * --ignore-whitespace\n      \n       diff --git a/builtin/rebase.c b/builtin/rebase.c\n       --- a/builtin/rebase.c\n 11:  ff43593211 = 14:  db5e29bd81 git-prompt: change the prompt for interactive-based rebases\n  -:  ---------- > 15:  413e190ac9 rebase: drop '-i' from the reflog for interactive-based rebases\n 12:  99388f24e5 ! 16:  170be283a8 rebase tests: mark tests specific to the am-backend with --am\n     @@ -228,40 +228,6 @@\n       \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n       }\n      \n     - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\n     - --- a/t/t3433-rebase-options-compatibility.sh\n     - +++ b/t/t3433-rebase-options-compatibility.sh\n     -@@\n     - \tnew line 2\n     - \tline 3\n     - \tEOF\n     --\ttest_must_fail git rebase main side &&\n     -+\ttest_must_fail git rebase --am main side &&\n     - \tgit rebase --abort &&\n     --\tgit rebase --ignore-whitespace main side &&\n     -+\tgit rebase --am --ignore-whitespace main side &&\n     - \ttest_cmp expect file\n     - '\n     - \n     -@@\n     - \n     - test_expect_success '--committer-date-is-author-date works with am backend' '\n     - \tgit commit --amend &&\n     --\tgit rebase --committer-date-is-author-date HEAD^ &&\n     -+\tgit rebase --am --committer-date-is-author-date HEAD^ &&\n     - \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n     - \tgit show HEAD --pretty=\"format:%ci\" >committertime &&\n     - \ttest_cmp authortime committertime\n     -@@\n     - # sets to +0530.\n     - test_expect_success '--ignore-date works with am backend' '\n     - \tgit commit --amend --date=\"$GIT_AUTHOR_DATE\" &&\n     --\tgit rebase --ignore-date HEAD^ &&\n     -+\tgit rebase --am --ignore-date HEAD^ &&\n     - \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n     - \tgrep \"+0000\" authortime\n     - '\n     -\n       diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\n       --- a/t/t5407-post-rewrite-hook.sh\n       +++ b/t/t5407-post-rewrite-hook.sh\n 13:  c2ba6317bf = 17:  1e3d4066c4 rebase tests: repeat some tests using the merge backend instead of am\n 14:  8bec6df51a = 18:  9b4ac83d2d rebase: make the backend configurable via config setting\n 15:  044853fd61 = 19:  859a4a94d7 rebase: change the default backend from \"am\" to \"merge\"\n\n-- \ngitgitgadget\n"},{"id":"389865","messageId":"8d731fa39c3c51cf33e1e105e756d2bf34a8f9a7.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 06/19] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:20Z","receivedAt":"2020-01-16T06:14:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe GIT_QUIET environment variable was used to signal the non-am\nbackends that the rebase should perform quietly.  The preserve-merges\nbackend does not make use of the quiet flag anywhere (other than to\nwrite out its state whenever it writes state), and this mechanism was\nbroken in the conversion from shell to C.  Since this environment\nvariable was specifically designed for scripts and the only backend that\nwould still use it is no longer a script, just gut this code.\n\nA subsequent commit will fix --quiet for the interactive/merge backend\nin a different way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 6 ++----\n sequencer.c      | 6 ++----\n 2 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex c299869e7b..57875485df 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t   opts->onto ? oid_to_hex(&opts->onto->object.oid) : \"\");\n \twrite_file(state_dir_path(\"orig-head\", opts), \"%s\",\n \t\t   oid_to_hex(&opts->orig_head));\n-\twrite_file(state_dir_path(\"quiet\", opts), \"%s\",\n-\t\t   opts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n+\tif (!(opts->flags & REBASE_NO_QUIET))\n+\t\twrite_file(state_dir_path(\"quiet\", opts), \"%s\", \"\");\n \tif (opts->flags & REBASE_VERBOSE)\n \t\twrite_file(state_dir_path(\"verbose\", opts), \"%s\", \"\");\n \tif (opts->strategy)\n@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"revisions\", opts->revisions);\n \tadd_var(&script_snippet, \"restrict_revision\", opts->restrict_revision ?\n \t\toid_to_hex(&opts->restrict_revision->object.oid) : NULL);\n-\tadd_var(&script_snippet, \"GIT_QUIET\",\n-\t\topts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n \tsq_quote_argv_pretty(&buf, opts->git_am_opts.argv);\n \tadd_var(&script_snippet, \"git_am_opt\", buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/sequencer.c b/sequencer.c\nindex 354d0b5a38..078a68eaf3 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2568,8 +2568,6 @@ static void write_strategy_opts(struct replay_opts *opts)\n int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\t      struct commit *onto, const char *orig_head)\n {\n-\tconst char *quiet = getenv(\"GIT_QUIET\");\n-\n \tif (head_name)\n \t\twrite_file(rebase_path_head_name(), \"%s\\n\", head_name);\n \tif (onto)\n@@ -2578,8 +2576,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \tif (orig_head)\n \t\twrite_file(rebase_path_orig_head(), \"%s\\n\", orig_head);\n \n-\tif (quiet)\n-\t\twrite_file(rebase_path_quiet(), \"%s\\n\", quiet);\n+\tif (opts->quiet)\n+\t\twrite_file(rebase_path_quiet(), \"%s\", \"\");\n \tif (opts->verbose)\n \t\twrite_file(rebase_path_verbose(), \"%s\", \"\");\n \tif (opts->strategy)\n-- \ngitgitgadget\n\n"},{"id":"389869","messageId":"b8c087d6fb12f7ca06848d04a75c29430183b4aa.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 10/19] rebase: allow more types of rebases to fast-forward","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:24Z","receivedAt":"2020-01-16T06:14:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we dis-allowed rebases using the interactive backend from\nperforming a fast-forward to short-circuit the rebase operation.  This\nmade sense for explicitly interactive rebases and some implicitly\ninteractive rebases, but certainly became overly stringent when the\nmerge backend was re-implemented via the interactive backend.\n\nJust as the am-based rebase has always had to disable the fast-forward\nbased on a variety of conditions or flags (e.g. --signoff, --whitespace,\netc.), we need to do the same but now with a few more options.  However,\ncontinuing to use REBASE_FORCE for tracking this is problematic because\nthe interactive backend used it for a different purpose.  (When\nREBASE_FORCE wasn't set, the interactive backend would not fast-forward\nthe whole series but would fast-forward individual \"pick\" commits at the\nbeginning of the todo list, and then a squash or something would cause\nit to start generating new commits.)  So, introduce a new\nallow_preemptive_ff flag contained within cmd_rebase() and use it to\ntrack whether we are going to allow a pre-emptive fast-forward that\nshort-circuits the whole rebase.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c               | 18 ++++++++++++++----\n t/t3432-rebase-fast-forward.sh |  2 ++\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex fe1175af32..4b7d8fc908 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n+\tint allow_preemptive_ff = 1;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t    state_dir_base, cmd_live_rebase, buf.buf);\n \t}\n \n+\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n+\t    (action != ACTION_NONE) ||\n+\t    (exec.nr > 0) ||\n+\t    options.autosquash) {\n+\t\tallow_preemptive_ff = 0;\n+\t}\n+\n \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n \t\tconst char *option = options.git_am_opts.argv[i], *p;\n \t\tif (!strcmp(option, \"--committer-date-is-author-date\") ||\n \t\t    !strcmp(option, \"--ignore-date\") ||\n \t\t    !strcmp(option, \"--whitespace=fix\") ||\n \t\t    !strcmp(option, \"--whitespace=strip\"))\n-\t\t\toptions.flags |= REBASE_FORCE;\n+\t\t\tallow_preemptive_ff = 0;\n \t\telse if (skip_prefix(option, \"-C\", &p)) {\n \t\t\twhile (*p)\n \t\t\t\tif (!isdigit(*(p++)))\n@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t/*\n \t * Check if we are already based on onto with linear history,\n \t * in which case we could fast-forward without replacing the commits\n-\t * with new commits recreated by replaying their changes. This\n-\t * optimization must not be done if this is an interactive rebase.\n+\t * with new commits recreated by replaying their changes.\n+\t *\n+\t * Note that can_fast_forward() initializes merge_base, so we have to\n+\t * call it before checking allow_preemptive_ff.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\t    allow_preemptive_ff) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 7432c0e241..40388ccf9f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -30,6 +30,8 @@ test_rebase_same_head () {\n \tshift &&\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\n \n test_rebase_same_head_ () {\n-- \ngitgitgadget\n\n"},{"id":"389866","messageId":"8c5b5b5133097de0b932a3bbfe1dc4709e3c49c7.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 09/19] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:23Z","receivedAt":"2020-01-16T06:14:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3432 had several stress tests for can_fast_forward(), whose intent was\nto ensure we were using the optimization of just fast forwarding when\npossible.  However, these tests verified that fast forwards had happened\nbased on the output that rebase printed to the terminal.  We can instead\ntest more directly that we actually fast-forwarded by checking the\nreflog, which also has the side effect of making the tests applicable\nfor the merge/interactive backend.\n\nThis change does lose the distinction between \"noop\" and \"noop-force\",\nbut as stated in commit c9efc216830f (\"t3432: test for --no-ff's\ninteraction with fast-forward\", 2019-08-27) which introduced that\ndistinction: \"These tests aren't supposed to endorse the status quo,\njust test for what we're currently doing.\".\n\nThis change does not actually run these tests with the merge/interactive\nbackend; instead this is just a preparatory commit.  A subsequent commit\nwhich fixes can_fast_forward() to work with that backend will then also\nchange t3432 to add tests of that backend as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------\n 1 file changed, 22 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 92f95b57da..7432c0e241 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,19 +44,15 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tcp .git/logs/HEAD expect &&\n \t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n-\t\t\t# Must check this case first, for 'is up to\n-\t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\told=\\$(wc -l <expect) &&\n+\t\t\ttest_line_count '-gt' \\$old .git/logs/HEAD\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout &&\n-\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n-\t\telif test $what = noop-force\n-\t\tthen\n-\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n+\t\t\ttest_cmp expect .git/logs/HEAD\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -71,14 +67,14 @@ test_rebase_same_head_ () {\n \n changes='no changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '\n \n changes='our changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head success noop same success work diff --keep-base master\n test_rebase_same_head success noop same success work diff --keep-base\n-- \ngitgitgadget\n\n"},{"id":"389867","messageId":"9f66229d5cc6713bb237602f04fecbb33ade1160.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 05/19] t3406: simplify an already simple test","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:19Z","receivedAt":"2020-01-16T06:14:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen the merge backend was re-implemented on top of the interactive\nbackend, the output of rebase --merge changed a little.  This change\nallowed this test to be simplified, though it wasn't noticed until now.\nSimplify the testcase a little.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3406-rebase-message.sh | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex b393e1e9fe..0c2c569f95 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -18,11 +18,8 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'rebase -m' '\n-\tgit rebase -m master >report &&\n-\t>expect &&\n-\tsed -n -e \"/^Already applied: /p\" \\\n-\t\t-e \"/^Committed: /p\" report >actual &&\n-\ttest_cmp expect actual\n+\tgit rebase -m master >actual &&\n+\ttest_must_be_empty actual\n '\n \n test_expect_success 'rebase against master twice' '\n-- \ngitgitgadget\n\n"},{"id":"389877","messageId":"0acefa988b14ca24966b31a4a7c0bb7bb31f10ba.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 08/19] rebase: fix handling of restrict_revision","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:22Z","receivedAt":"2020-01-16T06:14:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nrestrict_revision in the original shell script was an excluded revision\nrange.  It is also treated that way by the am-backend.  In the\nconversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\nimplement the logic to initialize $revisions in C\", 2018-08-28)), the\ninteractive-backend accidentally treated it as a positive revision\nrather than a negated one.\n\nThis was missed as there were no tests in the testsuite that tested an\ninteractive rebase with fork-point behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  |  4 ++--\n t/t3400-rebase.sh | 20 +++++++++++++++++++-\n 2 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex e8d518ac8d..fe1175af32 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \n \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n \tif (opts->restrict_revision)\n-\t\targv_array_push(&make_script_args,\n-\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n+\t\targv_array_pushf(&make_script_args, \"^%s\",\n+\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n \n \tret = sequencer_make_script(the_repository, &todo_list.buf,\n \t\t\t\t    make_script_args.argc, make_script_args.argv,\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 79762b989a..71fd6396cd 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n \tgit rebase master\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n \tgit checkout -b default-base master &&\n \tgit checkout -b default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual &&\n+\tgit checkout default-base &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout default &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+\tgit checkout -B default-base master &&\n+\tgit checkout -B default topic &&\n+\tgit config branch.default.remote . &&\n+\tgit config branch.default.merge refs/heads/default-base &&\n \tgit rebase &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n-- \ngitgitgadget\n\n"},{"id":"389873","messageId":"859a4a94d7f30629886cbe1b55501b1d7371ebd2.1579155274.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 19/19] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:33Z","receivedAt":"2020-01-16T06:14:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe am-backend drops information and thus limits what we can do:\n\n  * lack of full tree information from the original commits means we\n    cannot do directory rename detection and warn users that they might\n    want to move some of their new files that they placed in old\n    directories to prevent their becoming orphaned.[1]\n  * reduction in context from only having a few lines beyond those\n    changed means that when context lines are non-unique we can apply\n    patches incorrectly.[2]\n  * lack of access to original commits means that conflict marker\n    annotation has less information available.\n\nAlso, the merge/interactive backend have far more abilities, appear to\ncurrently have a slight performance advantage[3] and have room for more\noptimizations than the am backend[4] (and work is underway to take\nadvantage of some of those possibilities).\n\n[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt           |  2 +-\n builtin/rebase.c                       |  4 ++--\n t/t5520-pull.sh                        | 10 ++++++----\n t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n 4 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 5230084be9..880e8bff5a 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -313,7 +313,7 @@ See also INCOMPATIBLE OPTIONS below.\n --merge::\n \tUse merging strategies to rebase.  When the recursive (default) merge\n \tstrategy is used, this allows rebase to be aware of renames on the\n-\tupstream side.\n+\tupstream side.  This is the default.\n +\n Note that a rebase merge works by replaying each commit from the working\n branch on top of the <upstream> branch.  Because of this, when a merge\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b96de5809e..5c5526f42e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -101,7 +101,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"am\",\t  \t\\\n+\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\toptions.type = REBASE_MERGE;\n+\t\t\timply_interactive(&options, \"--merge\");\n \t\telse if (!strcmp(options.default_backend, \"am\"))\n \t\t\toptions.type = REBASE_AM;\n \t\telse\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 3fff6a06fa..4f9e7f7ff6 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '\n \ttest_tick &&\n \tgit commit -m \"Create conflict\" seq.txt &&\n \ttest_must_fail git pull --rebase . seq 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success 'failed --rebase shows advice' '\n@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '\n \tgit checkout -f -b fails-to-rebase HEAD^ &&\n \ttest_commit v2-without-cr file \"2\" file2-lf &&\n \ttest_must_fail git pull --rebase . diverging 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success '--rebase fails with multiple branches' '\n@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull --rebase &&\n-\t\tfind .git/rebase-apply -name \"000*\" >patches &&\n-\t\ttest_line_count = 1 patches\n+\t\tcat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&\n+\t\tgrep -v -e \\# -e ^$ work >patches &&\n+\t\ttest_line_count = 1 patches &&\n+\t\trm -f work\n \t)\n '\n \ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex dbe8deac0d..aec45bca3b 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' \"\n \n \n test_expect_success 'check that rebase really failed' '\n-\ttest -d .git/rebase-apply\n+\tgit status >output &&\n+\tgrep currently.rebasing output\n '\n \n test_expect_success 'resolve, continue the rebase and dcommit' \"\n-- \ngitgitgadget\n"},{"id":"389870","messageId":"9b4ac83d2d09aea5d6072a9ee29687e97a3573aa.1579155274.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 18/19] rebase: make the backend configurable via config setting","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:32Z","receivedAt":"2020-01-16T06:14:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt |  8 ++++++++\n builtin/rebase.c                | 31 ++++++++++++++++++++++++-------\n 2 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex d98e32d812..e6ae30c999 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -5,6 +5,14 @@ rebase.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+rebase.backend::\n+\tDefault backend to use for rebasing.  Possible choices are\n+\t'am' or 'merge' (note that the merge backend is sometimes also\n+\trefered to as the interactive backend or the interactive\n+\tmachinery elsewhere in the docs).  Also, in the future, if the\n+\tmerge backend gains all remaining capabilities of the am\n+\tbackend, this setting may become unused.\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ecd23d3ee7..b96de5809e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -60,6 +60,7 @@ enum empty_type {\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n+\tconst char *default_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -100,6 +101,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n+\t\t.default_backend = \"am\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)\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+\n \treturn git_default_config(var, value, data);\n }\n \n@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n+\t\tif (i >= 0) {\n+\t\t\tif (is_interactive(&options))\n+\t\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t\t      \"interactive or merge options\"));\n+\t\t\telse\n+\t\t\t\toptions.type = REBASE_AM;\n+\t\t}\n+\t}\n+\n+\tif (options.type == REBASE_UNSPECIFIED) {\n+\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"am\"))\n+\t\t\toptions.type = REBASE_AM;\n+\t\telse\n+\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n+\t\t\t    options.default_backend);\n \t}\n \n \tswitch (options.type) {\n@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n-\t\t/* the default rebase backend is `--am` */\n-\t\toptions.type = REBASE_AM;\n-\t\toptions.state_dir = apply_dir();\n-\t\tbreak;\n+\t\tBUG(\"options.type was just set above; should be unreachable.\");\n \t}\n \n \tif (options.empty == EMPTY_UNSPECIFIED) {\n-- \ngitgitgadget\n\n"},{"id":"389872","messageId":"170be283a851322afffa1b74e7d3eb366eb2da50.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 16/19] rebase tests: mark tests specific to the am-backend with --am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:30Z","receivedAt":"2020-01-16T06:14:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have many rebase tests in the testsuite, and often the same test is\nrepeated multiple times just testing different backends.  For those\ntests that were specifically trying to test the am backend, add the --am\nflag.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3400-rebase.sh                 | 10 +++++-----\n t/t3401-rebase-and-am-rename.sh   |  4 ++--\n t/t3404-rebase-interactive.sh     |  2 +-\n t/t3406-rebase-message.sh         | 12 ++++++------\n t/t3407-rebase-abort.sh           |  6 +++---\n t/t3420-rebase-autostash.sh       |  2 +-\n t/t3425-rebase-topology-merges.sh |  8 ++++----\n t/t3432-rebase-fast-forward.sh    |  4 ++--\n t/t5407-post-rewrite-hook.sh      | 12 ++++++------\n t/t7512-status-help.sh            | 12 ++++++------\n 10 files changed, 36 insertions(+), 36 deletions(-)\n\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 71fd6396cd..0a491f2363 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \n test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase -q master >output.out 2>&1 &&\n+\tgit rebase --am -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex a0b9438b22..50803958fd 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase (am): directory rename detected' '\n+test_expect_failure 'rebase --am: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase A &&\n+\t\tgit -c merge.directoryRenames=true rebase --am A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a31583eb2f..f964b2cd41 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 0c2c569f95..7ce617fc1f 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase master >out &&\n+\tgit rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase master >out &&\n+\tgit rebase --force-rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase master topic >out &&\n+\tgit rebase --am master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase topic >out &&\n+\tgit rebase --am topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase reflog-onto &&\n+\tgit rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 910f218284..3e31826170 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --quit' '\n+test_expect_success 'rebase --am --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase master &&\n+\ttest_must_fail git rebase --am master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 5f7e73cf83..3816159e20 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex fd8efe84fe..19700b025b 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' ''\n+test_run_rebase success 'n o e' --am\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 40388ccf9f..4b3cecce56 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 7344253bfb..a8a73616e4 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase' '\n+test_expect_success 'git rebase --am' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip' '\n+test_expect_success 'git rebase --am --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip the last one' '\n+test_expect_success 'git rebase --am --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto D A &&\n+\ttest_must_fail git rebase --am --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 66d7a62797..d22b0acf2a 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase in progress before rebase --continue' '\n+test_expect_success 'status when rebase --am in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\n-- \ngitgitgadget\n\n"},{"id":"389875","messageId":"413e190ac9ae1e701a807a6c846789ae7df35896.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 15/19] rebase: drop '-i' from the reflog for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:29Z","receivedAt":"2020-01-16T06:15:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nA large variety of rebase types are supported by the interactive\nmachinery, not just the explicitly interactive ones.  These all share\nthe same code and write the same reflog messages, but the \"-i\" moniker\nin those messages doesn't really have much meaning.  It also becomes\nsomewhat distracting once we switch the default from the am-backend to\nthe interactive one.  Just remove the \"-i\" from these messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c              |  2 +-\n sequencer.c                   | 12 ++++++------\n t/t3404-rebase-interactive.sh | 10 +++++-----\n 3 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 6884590258..ecd23d3ee7 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)\n \tif (env && strcmp(\"rebase\", env))\n \t\treturn; /* only override it if it is \"rebase\" */\n \n-\tstrbuf_addf(&buf, \"rebase -i (%s)\", options->action);\n+\tstrbuf_addf(&buf, \"rebase (%s)\", options->action);\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);\n \tstrbuf_release(&buf);\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 078a68eaf3..cca503c11b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)\n \t\t\tchar *eol = strchr(p, '\\n');\n \t\t\tif (eol)\n \t\t\t\t*eol = '\\0';\n-\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0) {\n+\t\t\tif (delete_ref(\"(rebase) cleanup\", p, NULL, 0) < 0) {\n \t\t\t\twarning(_(\"could not delete '%s'\"), p);\n \t\t\t\tret = -1;\n \t\t\t}\n@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)\n \tcase REPLAY_PICK:\n \t\treturn N_(\"cherry-pick\");\n \tcase REPLAY_INTERACTIVE_REBASE:\n-\t\treturn N_(\"rebase -i\");\n+\t\treturn N_(\"rebase\");\n \t}\n \tdie(_(\"unknown action: %d\"), opts->action);\n }\n@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t/*\n \t\t * TRANSLATORS: %s will be \"revert\", \"cherry-pick\" or\n-\t\t * \"rebase -i\".\n+\t\t * \"rebase\".\n \t\t */\n \t\treturn error(_(\"%s: Unable to write new index file\"),\n \t\t\t_(action_name(opts)));\n@@ -2885,8 +2885,8 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tint next = todo_list->current, offset, fd;\n \n \t/*\n-\t * rebase -i writes \"git-rebase-todo\" without the currently executing\n-\t * command, appending it to \"done\" instead.\n+\t * interactive backend writes \"git-rebase-todo\" without the currently\n+\t * executing command, appending it to \"done\" instead.\n \t */\n \tif (is_rebase_i(opts))\n \t\tnext++;\n@@ -3197,7 +3197,7 @@ static int do_label(struct repository *r, const char *name, int len)\n \t\treturn error(_(\"illegal label name: '%.*s'\"), len, name);\n \n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n-\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\tstrbuf_addf(&msg, \"rebase (label) '%.*s'\", len, name);\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction) {\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex c41531f349..a31583eb2f 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' '\n '\n \n test_expect_success 'reflog for the branch shows correct finish message' '\n-\tprintf \"rebase -i (finish): refs/heads/branch1 onto %s\\n\" \\\n+\tprintf \"rebase (finish): refs/heads/branch1 onto %s\\n\" \\\n \t\t\"$(git rev-parse branch2)\" >expected &&\n \tgit log -g --pretty=%gs -1 refs/heads/branch1 >actual &&\n \ttest_cmp expected actual\n@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' '\n \tgit branch -f branch-reflog-test H &&\n \tgit rebase -i --onto I F branch-reflog-test &&\n \tcat >expect <<-\\EOF &&\n-\trebase -i (finish): returning to refs/heads/branch-reflog-test\n-\trebase -i (pick): H\n-\trebase -i (pick): G\n-\trebase -i (start): checkout I\n+\trebase (finish): returning to refs/heads/branch-reflog-test\n+\trebase (pick): H\n+\trebase (pick): G\n+\trebase (start): checkout I\n \tEOF\n \tgit reflog -n4 HEAD |\n \tsed \"s/[^:]*: //\" >actual &&\n-- \ngitgitgadget\n\n"},{"id":"389874","messageId":"1e3d4066c40d914fc2f0f290d6794952daead346.1579155274.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 17/19] rebase tests: repeat some tests using the merge backend instead of am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:31Z","receivedAt":"2020-01-16T06:15:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn order to ensure the merge/interactive backend gets similar coverage\nto the am one, add some tests for cases where previously only the am\nbackend was tested.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t5520-pull.sh                   | 17 +++++++++++++++--\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--\n 2 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 602d996a33..3fff6a06fa 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -277,14 +277,27 @@ test_expect_success '--rebase' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--rebase fast forward' '\n+test_expect_success '--rebase (merge) fast forward' '\n \tgit reset --hard before-rebase &&\n \tgit checkout -b ff &&\n \techo another modification >file &&\n \tgit commit -m third file &&\n \n \tgit checkout to-rebase &&\n-\tgit pull --rebase . ff &&\n+\tgit -c rebase.backend=merge pull --rebase . ff &&\n+\ttest_cmp_rev HEAD ff &&\n+\n+\t# The above only validates the result.  Did we actually bypass rebase?\n+\tgit reflog -1 >reflog.actual &&\n+\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\ttest_cmp reflog.expected reflog.fuzzy\n+'\n+\n+test_expect_success '--rebase (am) fast forward' '\n+\tgit reset --hard before-rebase &&\n+\n+\tgit -c rebase.backend=am pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 860542aad0..d383ce8130 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n-test_expect_success 'rebase describes fake ancestor base' '\n+test_expect_success 'rebase --merge describes parent of commit being picked' '\n \ttest_create_repo rebase &&\n \t(\n \t\tcd rebase &&\n@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '\n \t\ttest_commit master file &&\n \t\tgit checkout -b side HEAD^ &&\n \t\ttest_commit side file &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&\n+\t\tgrep \"||||||| parent of\" file\n+\t)\n+'\n+\n+test_expect_success 'rebase --am describes fake ancestor base' '\n+\t(\n+\t\tcd rebase &&\n+\t\tgit rebase --abort &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"389876","messageId":"5478c730acad0423399de018f2099c07fefbb190.1579155273.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v4 13/19] rebase: add an --am option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-01-16T06:14:27Z","receivedAt":"2020-01-16T06:15:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCurrently, this option doesn't do anything except error out if any\noptions requiring the interactive-backend are also passed.  However,\nwhen we make the default backend configurable later in this series, this\nflag will provide a way to override the config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 11 ++++++++++-\n builtin/rebase.c             | 18 +++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex df02d76e2d..5230084be9 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--am:\n+\tUse git-am internally to rebase.  This option may become a\n+\tno-op in the future once the interactive backend handles\n+\teverything the am one does.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --empty={drop,keep,ask}::\n \tHow to handle commits that are not empty to start and are not\n \tclean cherry-picks of any upstream commit, but which become\n@@ -376,7 +383,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.\n+\tever ignored.  Implies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -416,6 +423,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThese flags are passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n+\tImplies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -559,6 +567,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n+ * --am\n  * --committer-date-is-author-date\n  * --ignore-date\n  * --ignore-whitespace\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 55a0b2a288..6884590258 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \treturn res && is_linear_history(onto, head);\n }\n \n+static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_AM;\n+\n+\treturn 0;\n+}\n+\n /* -i followed by -m is still -i */\n static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n {\n@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n+\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n+\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n \t\t\tN_(\"use merging strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc) {\n+\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n \t\t/* all am options except -q are compatible only with --am */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-- \ngitgitgadget\n\n"},{"id":"389878","messageId":"CABPp-BGqWDo0vkY_zBZ6n+2T92W2iR90=41oJO_Ae1yZP8NO0A@mail.gmail.com","threadId":"52496","inReplyTo":"nycvar.QRO.7.76.6.2001120942460.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-16T06:32:53Z","receivedAt":"2020-01-16T06:33:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Sun, Jan 12, 2020 at 9:59 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi,\n>\n> On Sat, 11 Jan 2020, Phillip Wood wrote:\n>\n> > On 11/01/2020 01:16, Elijah Newren wrote:\n> > >\n> > > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n> > > >\n> > > > Elijah Newren via GitGitGadget wrote:\n> > > >\n> > > >   1. \"git rebase --am\" does not invoke the post-commit hook, but \"git\n> > > >      rebase --merge\" does.  Is this behavior change intended?\n> > > >\n> > > >      Noticed because jiri[1] installs a post-commit hook that warns\n> > > >      about commits on detached HEAD, so this change makes rebases more\n> > > >      noisy in repositories that were set up using jiri.\n> >\n> > Perhaps that hook could learn not to warn if a branch is being rebased?\n> > git could be more helpful there by having a porcelain option to status\n> > that prints the branch name if we're rebasing (`git worktree --list`\n> > shows the branch correctly when it's being rebased but does not (yet - I\n> > have a patch to do it) mark the current worktree so isn't very helpful.)\n> >\n> > > I've never used a post-commit hook or seen one in the wild.  Certainly\n> > > wasn't intentional, but it's not clear to me if it's wrong or right\n> > > either.  I don't see why it would make sense to distinguish between\n> > > any of git rebase --am/--merge/--interactive, but it isn't too\n> > > surprising that by historical accident the two rebase backends which\n> > > happened to call git-commit behind the scenes would call a post-commit\n> > > hook and the other rebase backend that didn't call git-commit\n> > > wouldn't.\n> >\n> > Looking through the history the am based rebase has never run the post-commit\n> > hook as am has its own set of hooks and the scripted version used commit-tree.\n> > The merge based rebase ran `git commit` which ran the post commit hook. The\n> > interactive rebase ran the hook until and I broke it in a356ee4659b\n> > (\"sequencer: try to commit without forking 'git commit'\", 2017-11-24) and\n> > after I fixed it in 4627bc777e (\"sequencer: run post-commit hook\",\n> > 2019-10-15). As it was broken for two years with no one noticing it can't be\n> > that popular.\n>\n> Maybe a crazy idea, but maybe not: how about running the `post-commit`\n> hook _only_ if `--merge` was specified explicitly, and in that case (and\n> guarded behind a check verifying that the `post-commit` hook _actually_\n> exists _and_ is executable) warn the user that this hook won't be run in\n> future versions?\n>\n> To make things better for users who actually want to run that hook during\n> rebases, we could introduce a config option, say,\n> `rebase.runPostCommitHook` that is a tri-state (`true`, `false`,\n> `onlyForDashDashMerge`, at first defaulting to the last, eventually to\n> `false`).\n>\n> Crazy? Or helpful?\n\nSeems crazy.  Why would you want it just for --merge?  If anything, I\nwould think --merge should be most like --am; if some mode of rebase\nwere to be considered special, I'd think it'd only be the _explicitly_\ninteractive case.  But I don't see the justification for treating any\nof the rebase modes differently.  I think the hook should be on for\nall of them, or off for all of them, and I could go either way.\n\n(Honestly, it's tempting to just fix the fact that the interactive\nbackend needlessly forks a \"git commit\" process by having it commit on\nits own much like builtin/merge.c does.  Then omit calling the\npost-commit hook and it behaves the same as the am backend and no one\nin the world notices because no one in the world uses or cares about\nthat hook except a few people at Google who happen to be used to the\nam-backend and even then aren't convinced whether invoking the hook or\nnot is right.  And if that's wrong and someone has a solid argument\nabout why and the harm that not calling it does, then big deal, we add\ncalling the hook sometime in the future...)\n\n> > > But the big question here, is what is correct behavior?  Should rebase\n> > > call the post-commit hook, or should it skip it?  I haven't any clue\n> > > what the answer to that is.\n> >\n> > It's creating a new commit so I lean towards thinking it should run the\n> > post-commit hook. As an example I have a post-commit hook that prints a\n> > warning if a commit is created on a branch that is being rewritten by\n> > one of my scripts in another worktree. There are pre-commit and\n> > pre-rebase hooks to try and prevent that, but the warning is there as a\n> > last resort if those hooks are by-passed.\n>\n> I guess you're right, it is quite surprising that the `post-commit` hook\n> is _not_ run for `--am` rebases even though commits are created.\n\nI guess part of the problem is whether people think of it as \"new\ncommits\" or just any commits.  \"New commits\" are created by \"git\ncommit\" and \"git merge\".  rebase and cherry-pick just create\nderivatives of existing commits.  Given the existence of the\npost-rewrite hook, one could argue that the distinction has merit.\nI'm not sure either way, so I'm glad Emily started investigating.  I'd\nrather not deal with that can of worms...  :-)\n\n> > > >   2. GIT_REFLOG_ACTION contains \"rebase -i\" even though the rebase is\n> > > >      not interactive.\n> >\n> > If this is important to people I think it should be easy enough to set\n> > GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long\n> > as it hasn't already been set by the user) rather than relying on\n> > sequencer.c to do it.\n>\n> I agree (but won't have time to implement it, so maybe I should shut up\n> already...)\n>\n> > > Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of\n> > > rebases which use the interactive machinery even if they aren't\n> > > explicitly interactive.  I've never seen the \"-i\" in the reflog\n> > > message defined, but clearly it has always been used whenever the\n> > > interactive machinery was in play regardless of whether the rebase was\n> > > interactive.  In that regard, I figured that --merge fit in rather\n> > > nicely.  (And I noted the fact that reflog messages were different\n> > > between the backends among the \"BEHAVIORAL DIFFERENCES\" section of\n> > > git-rebase.txt).  But if others think we should just drop the -i (much\n> > > as we did for the bash prompt), I'd be happy with that too.  If we go\n> > > that route, I think I'd rather drop the -i in the reflog for all\n> > > rebases, not just the\n> > > using-the-interactive-machinery-but-not-explicitly-interactive ones.\n> > >\n> > > >   3. In circumstances I haven't pinned down yet, we get the error\n> > > >      message \"invalid date format: @@2592000 +0000\":\n> > > >\n> > > >          $ git rebase --committer-date-is-author-date --onto branch_K\n> > > >          branch_L~1 branch_L\n> > > >          $ git checkout --theirs file\n> > > >          $ git add file\n> > > >          $ git rebase --continue\n> > > >          fatal: invalid date format: @@2592000 +0000\n> > > >          error: could not commit staged changes.\n> > > >\n> > > >      This isn't reproducible without --committer-date-is-author-date.\n> > > >      More context (the test where it happens) is in [2].\n> > >\n> > > Interesting.  Do you happen to know if this started happening with\n> > > ra/rebase-i-more-options, or did it just become an issue with\n> > > en/rebase-backend?  I looked around at the link you provided and feel\n> > > a bit confused; I'm not sure which test does this or how I'd\n> > > reproduce.\n> >\n> > I'm confused by the test as well. As ra/rebase-i-more-options only touched the\n> > sequencer then any bugs would only show up in this test (which runs a\n> > non-interactive rebase) once en/rbease-backend switched to that backend. It\n> > seems likely that ra/rebase-i-more-options is to blame.\n> >\n> > Jonathan - do you happen to know if your users create empty commits at all?\n> > and if so what do they expect rebase to do with them (and any that become\n> > empty when they are rebased) - cf\n> > https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>\n>\n> The double `@` looks very funny. I would be interested in seeing an MCVE.\n>\n> > > >   4. I suspect the exit status in the \"you need to resolve conflicts\"\n> > > >      case has changed.  With rebase --am, [3] would automatically\n> > > >      invoke rebase --abort when conflicts are present, but with rebase\n> > > >      --merge it does not.\n> > > >\n> > > > Known?\n> > >\n> > > Nope, but I would certainly hope that \"you need to resolve conflicts\"\n> > > would result in a non-zero exit status.  If it doesn't, that sounds\n> > > like a bug in the interactive backend that we need to fix.  I'll dig\n> > > in.\n>\n> Yes, exiting with status 0 would be a major bug, and I think it might even\n> be a bug that was introduced by me when I re-implemented the core loop of\n> the interactive rebase in C.\n\nOoh, that sounds interesting.  Do you have any more details?  My\nsimple testing here shows that we exit with status 1, so we shouldn't\nhave that problem unless perhaps there was something else in next\n(ra/rebase-i-more-options??) or some other special conditions that was\ncausing it.\n\n> But to me it sounds as if 4. is not so about the exit code but about\n> aborting immediately. I do not recall seeing --am rebases to abort,\n> though, but to exit with error (and I saw the same behavior in interactive\n> rebases).\n\nThe aborting \"immediately\" wasn't done by git but by the code calling\ngit; following Jonathan's link you'll see the code they are\ncomplaining about is:\n\nfunc tryRebase(jirix *jiri.X, project Project, branch string) (bool, error) {\n    scm := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))\n    if err := scm.Rebase(branch); err != nil {\n        err := scm.RebaseAbort()\n        return false, err\n    }\n    return true, nil\n}\n\nbut of course, the scm.Rebase() and scm.RebaseAbort() definitions\naren't within that file so I have no idea what is really being run.\n\n> We will need to see a reduced concrete example (preferably as a new test\n> case) of the described behavior.\n\nYes, please.\n"},{"id":"389880","messageId":"20200116075810.GB242359@google.com","threadId":"52496","inReplyTo":"CABPp-BGqWDo0vkY_zBZ6n+2T92W2iR90=41oJO_Ae1yZP8NO0A@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-16T07:58:10Z","receivedAt":"2020-01-16T07:58:15Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Elijah Newren wrote:\n\n>                                          Then omit calling the\n> post-commit hook and it behaves the same as the am backend and no one\n> in the world notices because no one in the world uses or cares about\n> that hook except a few people at Google who happen to be used to the\n> am-backend\n\nJust responding to this part: I know this was a bit of thinking out\nloud, but the \"just a few people at Google\" bit is counter-productive.\nThe search Emily ran\n<https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>\nshows that it's fairly common to use a post-commit hook for\ndeployment, with scripts like\n\n\t#!/bin/bash\n\tunset GIT_INDEX_FILE\n\tgit --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -f\n\nor\n\n\t#!/bin/bash\n\t# Sync gh-pages branch with master\n\t#########################################\n\tgit checkout gh-pages\n\tgit rm -rf -q .\n\tgit checkout master -- .\n\tgit add .\n\tgit commit -am \"Syncing gh-pages with master\"\n\tgit checkout master\n\nAnd I'm not saying that selfishly --- obviously, from a selfish\nperspective, what you're proposing would change behavior the least and\nI'd end up with happy users. :)  I'm just trying to help with updating\nthe list's collective model of user behavior.\n\n(Actually, I want to remove jiri's post-commit hook --- so it is only\nthe example that revealed this behavior change and is not my\nmotivation for continuing to chime in in this thread.)\n\nThe deployment examples above seem like examples where the user would\nwant the script to run on \"git am\" (and on \"git merge --ff-only\", for\nthat matter) but not on the intermediate commits in \"git rebase\",\nsince when rebasing a multi-commit series, deploying earlier rebased\ncommits would cause the deployment to lose the benefit of later fixes.\n\n[...]\n> Ooh, that sounds interesting.  Do you have any more details?  My\n> simple testing here shows that we exit with status 1, so we shouldn't\n> have that problem unless perhaps there was something else in next\n> (ra/rebase-i-more-options??) or some other special conditions that was\n> causing it.\n\nI can set aside some more time to investigate that one early next\nweek.\n\nThanks for the quick answers --- it's been very helpful.\n\nSincerely,\nJonathan\n"},{"id":"389881","messageId":"20200116080624.GA253496@google.com","threadId":"52496","inReplyTo":"20200116075810.GB242359@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-16T08:06:24Z","receivedAt":"2020-01-16T08:06:28Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jonathan Nieder wrote:\n\n> \t#!/bin/bash\n> \t# Sync gh-pages branch with master\n> \t#########################################\n> \tgit checkout gh-pages\n> \tgit rm -rf -q .\n> \tgit checkout master -- .\n> \tgit add .\n> \tgit commit -am \"Syncing gh-pages with master\"\n> \tgit checkout master\n\nCorrection: I misread this one.  It was guarded with\n\n\tif [ `git rev-parse --abbrev-ref HEAD` == \"master\" ]; then\n\t\t...\n\tfi\n\nwhich makes all the difference.  Another example is to compute ctags:\n\n\t#!/bin/sh\n\n\t~/bin/hook_ctags.sh >/dev/null 2>&1 &\n\nHere's a fun one that updates a timesheet:\nhttps://github.com/GokhanArik/git-hooks-timesheet\n\nSorry for the noise,\nJonathan\n"},{"id":"389886","messageId":"nycvar.QRO.7.76.6.2001161146460.46@tvgsbejvaqbjf.bet","threadId":"52496","inReplyTo":"CABPp-BGqWDo0vkY_zBZ6n+2T92W2iR90=41oJO_Ae1yZP8NO0A@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-01-16T10:48:38Z","receivedAt":"2020-01-16T10:48:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Wed, 15 Jan 2020, Elijah Newren wrote:\n\n> On Sun, Jan 12, 2020 at 9:59 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Sat, 11 Jan 2020, Phillip Wood wrote:\n> >\n> > > On 11/01/2020 01:16, Elijah Newren wrote:\n> > > >\n> > > > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder\n> > > > <jrnieder@gmail.com> wrote:\n> >\n> > > > >   4. I suspect the exit status in the \"you need to resolve conflicts\"\n> > > > >      case has changed.  With rebase --am, [3] would automatically\n> > > > >      invoke rebase --abort when conflicts are present, but with rebase\n> > > > >      --merge it does not.\n> > > > >\n> > > > > Known?\n> > > >\n> > > > Nope, but I would certainly hope that \"you need to resolve\n> > > > conflicts\" would result in a non-zero exit status.  If it doesn't,\n> > > > that sounds like a bug in the interactive backend that we need to\n> > > > fix.  I'll dig in.\n> >\n> > Yes, exiting with status 0 would be a major bug, and I think it might\n> > even be a bug that was introduced by me when I re-implemented the core\n> > loop of the interactive rebase in C.\n>\n> Ooh, that sounds interesting.  Do you have any more details?  My simple\n> testing here shows that we exit with status 1, so we shouldn't have that\n> problem unless perhaps there was something else in next\n> (ra/rebase-i-more-options??) or some other special conditions that was\n> causing it.\n\nOh, I did not mean to say that it exits with status 0. I meant to say that\n_if_ it does, it would be terrible, and that my suspicion would be that\nthe re-implementation in C introduced that bug. But as you say, there is\nno bug, so for once, the re-implementation in C is not at fault! ;-)\n\nCiao,\nDscho\n"},{"id":"389890","messageId":"CABPp-BEPiG8OmST9z0obRGuS0Ern5RQsPcRzFEf9=KsE-Hzvbw@mail.gmail.com","threadId":"52496","inReplyTo":"20200116075810.GB242359@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-16T15:35:37Z","receivedAt":"2020-01-16T15:35:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 15, 2020 at 11:58 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> Elijah Newren wrote:\n>\n> >                                          Then omit calling the\n> > post-commit hook and it behaves the same as the am backend and no one\n> > in the world notices because no one in the world uses or cares about\n> > that hook except a few people at Google who happen to be used to the\n> > am-backend\n>\n> Just responding to this part: I know this was a bit of thinking out\n> loud, but the \"just a few people at Google\" bit is counter-productive.\n\nYes, you're right, I should do better.  Let me try again to express my\nthoughts in a more constructive way...\n\n> The search Emily ran\n> <https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>\n> shows that it's fairly common to use a post-commit hook for\n> deployment,\n\n* I actually looked through a dozen or two of those yesterday.  I came\naway assuming that many of those might be svn hooks\n(http://svnbook.red-bean.com/en/1.7/svn.ref.reposhooks.post-commit.html)\nor for other non-git version control systems.  Those that were for git\ndidn't seem like they'd matter whether or not they were called by\nrebase.  Which led me down a thought experiment...\n\n* sequencer.c has accumulated a bit of cruft.  Partially this was\nbecause it's conversion to c was somewhat minimal, e.g. it'd still\nfork other processes.  One of these other processes is \"git commit\".\nI think it's been part of the plan all along to remove any forked\nprocesses from sequencer.c and other things that were converted from\nshell.\n\n* What if, before being aware of this post-commit hook, someone had\nremoved the forking of \"git commit\" from sequencer.c?  I think it\nnearly certain that whoever did that wouldn't realize that strict\nback-compat would involve adding a call to a post-commit hook when\nthey did that, and thus all the non-am-based rebases and cherry-picks\nwould suddenly stop calling that hook.  Would anyone notice?\n\n* People have been using the interactive and merge backends for years\nfor lots of different rebase types with more and more joining.  We've\nnever had a report that users were annoyed by it calling the\npost-commit hook.  But, on the other hand, rebase-am has run for years\nwithout ever calling the post-commit hook and no one has complained.\nI don't believe interactivity is the deciding factor here, as we have\nseveral non-interactive types of rebases implemented by the\ninteractive backend (--keep-empty, --exec, --rebase-merges,\n--preserve-merges, --strategy, --merge) all of which are more similar\nin usecase to the am-backend than to the explicitly interactive\nusecase from a user perspective (i.e. \"Just run on all the commits\"\nvs. \"Let me edit and rearrange and drop and insert and whatnot\") and\nthus for which I'd assume similar user expectations for what hooks if\nany to call.\n\n* When the issue came up, Junio argued that \"post-commit\" can easily\nbe argued to mean \"whenever a commit is created\".  That seemed\neminently reasonable to me.  On the flip side, we do have both\n\"post-commit\" and \"post-rewrite\" hooks, which could argue that you'd\nwant to handle brand new commits (\"git commit\", \"git merge\")\ndifferently than tweaking existing ones (\"git rebase\", \"git\ncherry-pick\"), though explicitly interactive rebases are a\nmonkey-wrench here since they can be used to insert brand new commits\nin the middle of rewriting a big pile of commits (suggesting maybe\nthat only the explicitly interactive rebases should call\n\"post-commit\"?).\n\n* Five different people on this list have chimed in about this\n\"post-commit\" behavior, but I haven't seen a single argument for or\nagainst calling \"post-commit\" based on actual user expectation for\nthis kind of usecase (e.g. if done from a clean design).  All I've\nseen is folks talking about whether there are backward compatibility\nconcerns and/or what could be construed by the meaning of words like\n\"post-commit\".  In other words, I don't think any of us have a clue\nwhat is correct behavior if we were designing from scratch.\n\n* Since we don't seem to understand what \"correct\" behavior is...how\nmuch does or would \"incorrect\" behavior hurt?  What if we made a\nrandom judgement call about the post-commit hook and got it wrong?\nWell...\n\n   * If we changed am to also call the post-commit hook, we have some\nearly signal that there might be some folks who could comment, though\nI haven't yet seen a case where people would be hurt other than a\npossible slow-down which the user could probably fix by adjusting\ntheir scripts.  And in such cases it's easy to say \"post-commit\" means\nwe call the hook when a new commit is created; if you're doing\nsomething weird where you don't want to do work during the middle of a\nrebase your script should check for that case -- it has always been\ncalled by other rebase backends anyway.\n\n   * If we changed sequencer.c to stop forking a git-commit process\nand by proxy missed the post-commit hook, I strongly suspect no one\nwould notice or care.  But if they did, we could just point out how we\nhave both \"post-commit\" and \"post-rewrite\" hooks and that means we\n\"fixed\" our bug of calling the \"post-commit\" hook from other\nrebase/cherry-pick backends in the past by just calling the\npost-rewrite hook.  We can tell users they should add a post-rewrite\nhook, and, if they want, have it call their post-commit hook one or\nmore times.\n\n   * If we changed sequencer.c in a way that it stopped calling the\npost-commit hook, and someone did notice/care and did have a good\nsolid argument about correct behavior and that it should involve\ncalling \"post-commit\"...then we simply add it and the world is fixed\n(though we have to do the explaining that post-commit means whenever a\ncommit is created mentioned above).  And we provide users with a\nworkaround for older git versions where we tell them to have the\npost-rewrite hook invoke their own post-commit hook for them.\n\n\nIn short, while I don't know what \"correct\" behavior is other than\nthat the rebase backends ought to behave the same (or at least all the\nam-based rebases and the non-explicitly-interactive rebases\nimplemented by the interactive backend should all behave the same),\nI'm not yet seeing much damage from just picking an answer and\nimplementing it.\n\nAll that said, I'm really, really glad Emily decided to look into this\nso I don't have to argue or justify it from anything more than a\n\"backseat driver\" type of perspective.  :-)\n\n> with scripts like\n>\n>         #!/bin/bash\n>         unset GIT_INDEX_FILE\n>         git --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -f\n>\n> or\n>\n>         #!/bin/bash\n>         # Sync gh-pages branch with master\n>         #########################################\n>         git checkout gh-pages\n>         git rm -rf -q .\n>         git checkout master -- .\n>         git add .\n>         git commit -am \"Syncing gh-pages with master\"\n>         git checkout master\n>\n> And I'm not saying that selfishly --- obviously, from a selfish\n> perspective, what you're proposing would change behavior the least and\n> I'd end up with happy users. :)  I'm just trying to help with updating\n> the list's collective model of user behavior.\n>\n> (Actually, I want to remove jiri's post-commit hook --- so it is only\n> the example that revealed this behavior change and is not my\n> motivation for continuing to chime in in this thread.)\n>\n> The deployment examples above seem like examples where the user would\n> want the script to run on \"git am\" (and on \"git merge --ff-only\", for\n> that matter) but not on the intermediate commits in \"git rebase\",\n> since when rebasing a multi-commit series, deploying earlier rebased\n> commits would cause the deployment to lose the benefit of later fixes.\n\nWith your correction later in the thread that it's protected by\ncurrent branch == \"master\", this example doesn't concern me as much.\nThe ctags case is more interesting, but it's currently running on\nevery commit of rebase using the interactive backend already, and\nwhile it might slow things down to run on every commit it wouldn't\nbreak anything.  Plus, users can easily add a detached-head or\nin-the-middle-of-rebase check to their hook to avoid such a slow down\nif it is enough for them to notice so I'm not seeing much harm yet.\n\nSo, these are definitely interesting cases, but I'm still not seeing\narguments about whether the hook should be called from a user\nperspective, just whether users might in the short term be adversely\naffected due to assuming they previously built up scripts assuming\nonly certain types of rebases would ever be run.  (Not that I'm trying\nto discount the short term, because it obviously matters, but that I'm\nworried we're letting it be the sole deciding factor rather than\nthinking of correct behavior and possibly introducing transition plans\nand whatnot if needed.)\n\n> [...]\n> > Ooh, that sounds interesting.  Do you have any more details?  My\n> > simple testing here shows that we exit with status 1, so we shouldn't\n> > have that problem unless perhaps there was something else in next\n> > (ra/rebase-i-more-options??) or some other special conditions that was\n> > causing it.\n>\n> I can set aside some more time to investigate that one early next\n> week.\n\nThat'd be great, thanks.\n\n> Thanks for the quick answers --- it's been very helpful.\n>\n> Sincerely,\n> Jonathan\n"},{"id":"389901","messageId":"CABPp-BHODjbN3-snoFt4LDHGnoxhpDCQHhGVZp14gP1RZFp=hQ@mail.gmail.com","threadId":"52496","inReplyTo":"20200116080624.GA253496@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-16T16:18:30Z","receivedAt":"2020-01-16T16:18:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Thu, Jan 16, 2020 at 12:06 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> which makes all the difference.  Another example is to compute ctags:\n>\n>         #!/bin/sh\n>\n>         ~/bin/hook_ctags.sh >/dev/null 2>&1 &\n\nThought about this ctags case a bit more.  In contrast to the\ndeployment cases you brought up (which appear to be protected by a\nbranch check anyway), doesn't this one argue more that post-commit\nshould be run for all rebase types?  Sure, it'd take more computation\ntime, but if a user runs \"git pull --rebase\" and then is frustrated\nthat all their tags are extremely out of date then they may be kind of\nfrustrated.\n\nOf course, arguing against myself, we could always tell them to just\nimplement a post-rewrite hook.\n\n> Here's a fun one that updates a timesheet:\n> https://github.com/GokhanArik/git-hooks-timesheet\n\nSo...cherry-picking commits will pad your timesheet with duplicate\nwork on the same topic (because cherry-pick calls sequencer which\ncalls \"git commit\"), and so will interactive-based rebases, but\nrebase-am (the default rebase in the past) wouldn't.  Fun indeed...\n\nWouldn't such users view removing the post-commit hook from sequencer\nas \"harmful to their productivity\"?  And shouldn't we add the\npost-commit hook to rebase-am in order to \"increase their\nproductivity\"?  ;-)\n\n> Sorry for the noise,\n\nNo, these were great examples; thanks for pointing them out.  My\ncommentary about the timesheet is the only thing that is noise here.\n:-)\n"},{"id":"389914","messageId":"xmqqwo9rgo9v.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"20200116075810.GB242359@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-01-16T20:05:48Z","receivedAt":"2020-01-16T20:05:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> The deployment examples above seem like examples where the user would\n> want the script to run on \"git am\" (and on \"git merge --ff-only\", for\n> that matter) but not on the intermediate commits in \"git rebase\",\n> since when rebasing a multi-commit series, deploying earlier rebased\n> commits would cause the deployment to lose the benefit of later fixes.\n\nAgree.  The same thing can be said to \"git am\" accepting a series of\nmultiple patches, though.\n\n"},{"id":"389924","messageId":"20200116203521.GA71299@google.com","threadId":"52496","inReplyTo":"CABPp-BHODjbN3-snoFt4LDHGnoxhpDCQHhGVZp14gP1RZFp=hQ@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-16T20:35:22Z","receivedAt":"2020-01-16T20:35:26Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Elijah Newren wrote:\n\n> Thought about this ctags case a bit more.  In contrast to the\n> deployment cases you brought up (which appear to be protected by a\n> branch check anyway),\n\nTo be clear, one of the two deployment cases I mentioned (gh-pages) is\nprotected by a branch check, but the other (git --work-tree=srvroot\ncheckout) is not.\n\n>                       doesn't this one argue more that post-commit\n> should be run for all rebase types?  Sure, it'd take more computation\n> time, but if a user runs \"git pull --rebase\" and then is frustrated\n> that all their tags are extremely out of date then they may be kind of\n> frustrated.\n\nYes.  In fact I think all of these examples argue for having the\npost-commit hook run once on rebase completion (and on \"git am\"\ncompletion, \"git merge --ff-only\" completion, etc).\n\nOf course it's not hard to find other examples that argue against\nthat.  We provided a hook \"here's a notification that runs after git\ncommit runs\" without tailoring it to a particular need, and on one\nhand people came up with various creative ways to use it (yay!) but on\nthe other hand it's hard to maintain as Git's ways of making new\ncommits evolve.\n\n> Of course, arguing against myself, we could always tell them to just\n> implement a post-rewrite hook.\n\nThat's an interesting facet I hadn't thought about before.\n\nIt's tempting since it kind of minimizes the blast surface while\nproviding people a way to keep doing what they had wanted.\n\nHistorically \"git cherry-pick\" ran \"git commit\", causing it to run the\npost-commit hook.  There's some related discussion at\nhttps://lore.kernel.org/git/20f33df8-7ba8-af26-e0c8-16152345c85b@talktalk.net/\nof other side effects of having run \"git commit\".\n\n... oh! git cherry-pick doesn't run post-rewrite.  Interesting.\n\nTo summarize what we've discussed so far:\n\n A. run post-commit hook consistently in rebase --am and --merge modes\n\n    Advantages:\n    * consistent with historic \"rebase -i\" behavior\n    * supports hooks like ctags generation that want to update state to\n      match what has been committed in the worktree (especially when\n      preparing for the rebase to stop due to conflicts)\n    * consistent with other commands like \"git cherry-pick\" that run the\n      post-commit hook\n\n    Disadvantages:\n    * invokes hooks more often in a setting they are not used to being\n      invoked in\n    * slows down rebase\n    * when post-commit hooks are used for deployment, exposes\n      intermediate states in the middle of a rebase to the deployment\n      environment\n    * inconsistent with \"git am\"\n\n    Mitigations:\n    * could go through the normal warn about change, opt-in config,\n      opt-out config cycle to make the change smoother\n    * could provide new hooks (e.g., one run after a batch of objects\n      is created for applications similar to \"git gc --auto\", one run\n      when HEAD is updated for applications like the ctags one, one\n      run on ref update for applications like the deployment case) and\n      encourage authors to migrate to them\n    * could run the post-commit hook in \"git am\", too\n\n B. stop running the post-commit hook in rebase\n\n    Advantages:\n    * makes --am and --merge consistent with minimal user impact\n\n    Disadvantages:\n    * meaning of the post-commit hook remains a muddle\n    * in particular, \"git am\" and \"git rebase\" would not be consistent\n      with \"git cherry-pick\"\n\n    Mitigations:\n    * could provide new hooks for people to migrate to to replace that\n      muddle\n\n C. stop running the post-commit hook in rebase --merge or commit --amend\n\n    Advantages:\n    * produces a consistent definition of post-commit (\"it's about new\n      commits only\")\n    * hook authors get the same expressiveness using the post-rewrite hook\n\n    Disadvantages:\n    * the change to historical \"git commit --amend\" behavior is likely\n      to be surprising\n\n    Mitigations:\n    * could go through the warn, opt-in config, opt-out config cycle\n\nAny bits I've missed?\n\nThanks,\nJonathan\n"},{"id":"389936","messageId":"CABPp-BHiYDLRgPX4A1-8f-zgfsDqf6PkToStaV3ukC5Dsq=3-w@mail.gmail.com","threadId":"52496","inReplyTo":"20200116203521.GA71299@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-16T21:30:38Z","receivedAt":"2020-01-16T21:30:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jan 16, 2020 at 12:35 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> > Of course, arguing against myself, we could always tell them to just\n> > implement a post-rewrite hook.\n>\n> That's an interesting facet I hadn't thought about before.\n>\n> It's tempting since it kind of minimizes the blast surface while\n> providing people a way to keep doing what they had wanted.\n>\n> Historically \"git cherry-pick\" ran \"git commit\", causing it to run the\n\nWhat do you mean \"historically\"?  I'm pretty sure this piece of code\nis shared in the sequencer between rebase -i, rebase --merge, and\ncherry-pick, so all three currently call post-commit unless I'm\nmistaken.\n\n> post-commit hook.  There's some related discussion at\n> https://lore.kernel.org/git/20f33df8-7ba8-af26-e0c8-16152345c85b@talktalk.net/\n> of other side effects of having run \"git commit\".\n>\n> ... oh! git cherry-pick doesn't run post-rewrite.  Interesting.\n>\n> To summarize what we've discussed so far:\n>\n>  A. run post-commit hook consistently in rebase --am and --merge modes\n\nCould you spell these out a bit more?  I think by this option you mean\n\"Always run post-commit when commits are created\", which includes\nwhether those commits were created by git-commit, git-rebase (any\nbackend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but\nwith a glaring exception for fast-import and things that call it, much\nlike the exception post-rewrite already carves out for it).\n\nMuch as I don't see a reason to make rebase --merge different than\nrebase --am, I don't see a reason to differentiate between git-merge\nand git-commit.  I also don't see why rebase --am should be different\nthan am.\n\n>     Advantages:\n>     * consistent with historic \"rebase -i\" behavior\n>     * supports hooks like ctags generation that want to update state to\n>       match what has been committed in the worktree (especially when\n>       preparing for the rebase to stop due to conflicts)\n>     * consistent with other commands like \"git cherry-pick\" that run the\n>       post-commit hook\n>\n>     Disadvantages:\n>     * invokes hooks more often in a setting they are not used to being\n>       invoked in\n>     * slows down rebase\n>     * when post-commit hooks are used for deployment, exposes\n>       intermediate states in the middle of a rebase to the deployment\n>       environment\n>     * inconsistent with \"git am\"\n\nI don't want to introduce additional inconsistencies.  git-am and git\nmerge should be changed at the same time if we go this route.\n\n>     Mitigations:\n>     * could go through the normal warn about change, opt-in config,\n>       opt-out config cycle to make the change smoother\n>     * could provide new hooks (e.g., one run after a batch of objects\n>       is created for applications similar to \"git gc --auto\", one run\n>       when HEAD is updated for applications like the ctags one, one\n>       run on ref update for applications like the deployment case) and\n>       encourage authors to migrate to them\n>     * could run the post-commit hook in \"git am\", too\n\n\n\n>  B. stop running the post-commit hook in rebase\n\nAND sometimes in both cherry-pick and git-revert, depending on the\nnumber of commit(s) picked/reverted (see below).\n\nAlso, even if we go this route, I think the post-commit hook should be\nadded to git-merge whenever it creates a merge commit.\n\n>     Advantages:\n>     * makes --am and --merge consistent with minimal user impact\n\n...and also makes --interactive and --exec and other cases consistent too.\n\n>     Disadvantages:\n>     * meaning of the post-commit hook remains a muddle\n\nWhy?  \"Commands which create no more than one commit (git-commit,\ngit-merge, maybe single-commit git-revert or git-cherry-pick) call\npost-commit, commands which create several commits derived from others\n(git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call\npost-rewrite instead for performance reasons\" (with the standard\nexception for fast-import; also, git-merge could probably call\npost-rewrite in fast-forward cases since no new commit is created to\ncall post-commit on).\n\nOr we could just say that commands which can create multiple commits\n(cherry-pick & revert) only call post-rewrite even in cases where they\nare only called with one commit (since am and rebase can also be\ncalled on just one commit).\n\n>     * in particular, \"git am\" and \"git rebase\" would not be consistent\n>       with \"git cherry-pick\"\n\nYeah, I'd rather not introduce more inconsistencies; we should pick a\nrule and enforce it universally.  Thus, I'd say option B would\nprobably be thought of as the \"always creates at most one commit\" vs.\n\"creates multiple commits\" split for post-commit vs. post-rewrite.\n\n>     Mitigations:\n>     * could provide new hooks for people to migrate to to replace that\n>       muddle\n>\n>  C. stop running the post-commit hook in rebase --merge or commit --amend\n>\n>     Advantages:\n>     * produces a consistent definition of post-commit (\"it's about new\n>       commits only\")\n\nBut cherry-pick never creates \"new\" commits if commit --amend doesn't,\nso we'd also want to turn the post-commit hook off for cherry-pick,\nright?  And we'd want to turn post-commit on for git-merge, at least\nfor non-fast-forward cases since those create new commits.\n\n>     * hook authors get the same expressiveness using the post-rewrite hook\n\nIsn't this also an advantage for case B?  Any reason it was left off up there?\n\n>     Disadvantages:\n>     * the change to historical \"git commit --amend\" behavior is likely\n>       to be surprising\n\nThat is certainly one issue, but I'd say this case is riddled with\nproblems and is the most muddled of all.  Once you change git commit\n--amend behavior because it's about *new* commits only then you've got\na huge can of worms from rebase -i:\n\nFor interactive rebases, we have to avoid the post-commit hook for\nanything that is a pick, but if they break or edit and insert new\ncommits then we have to call the post-commit hook for those.  If the\nrebase stops due to a merge conflict, then the next commit created\nneeds to avoid the post-commit hook.  If they pick to squash or fixup\ncommits, then we have to deploy rename/break detection to determine\nwhether the commit is \"new\" or not and thus whether the post-commit\nhook should be triggered for it.  Actually, maybe we need the\nrename/break-detection logic when the rebase stops at a conflict too\ndue to user edits.  And then we start getting into questions about\nwhether the normal heuristics for rename/break-detection apply or if\n\"new-ness\" should have its own set of heuristics...\n\nAlso,  I can't tell if a reverted commit count as \"new\" or not.  Even\nif it does, what about the case of a reverted revert, or higher order\niterations of reverts (which I see occasionally)?\n\nDoes this definition mean we should use the git-cherry machinery on\nany new commits to search for \"newness\" (maybe even with an extension\nto search across all branches) before we decide whether to call the\npost-commit hook?\n\nI think case C is opening up a pandora's box of craziness.\n\n>     Mitigations:\n>     * could go through the warn, opt-in config, opt-out config cycle\n>\n> Any bits I've missed?\n>\n> Thanks,\n> Jonathan\n\n\nI think it was useful to phrase it this way and list the various\nadvantages, disadvantages, and mitigations; thanks.\n\nIs there anything I've missed in my additional details?  Anything\nsound crazy (beyond what I said I think case C leads to)?\n\n\nElijah\n"},{"id":"389948","messageId":"20200116223912.GB71299@google.com","threadId":"52496","inReplyTo":"CABPp-BHiYDLRgPX4A1-8f-zgfsDqf6PkToStaV3ukC5Dsq=3-w@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-01-16T22:39:12Z","receivedAt":"2020-01-16T22:39:16Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Elijah Newren wrote:\n> On Thu, Jan 16, 2020 at 12:35 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n\n>> Historically \"git cherry-pick\" ran \"git commit\", causing it to run the\n>\n> What do you mean \"historically\"?\n\nBefore 356ee4659bb (sequencer: try to commit without forking 'git\ncommit', 2017-11-24).  See also 4627bc777e9 (sequencer: run\npost-commit hook, 2019-10-15).\n\n>                                  I'm pretty sure this piece of code\n> is shared in the sequencer between rebase -i, rebase --merge, and\n> cherry-pick, so all three currently call post-commit unless I'm\n> mistaken.\n\nYep, that's right.\n\n[...]\n>>  A. run post-commit hook consistently in rebase --am and --merge modes\n>\n> Could you spell these out a bit more?  I think by this option you mean\n> \"Always run post-commit when commits are created\", which includes\n> whether those commits were created by git-commit, git-rebase (any\n> backend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but\n> with a glaring exception for fast-import and things that call it, much\n> like the exception post-rewrite already carves out for it).\n>\n> Much as I don't see a reason to make rebase --merge different than\n> rebase --am, I don't see a reason to differentiate between git-merge\n> and git-commit.  I also don't see why rebase --am should be different\n> than am.\n\nSure, that matches the general gist behind (A).\n\n[...]\n>>  B. stop running the post-commit hook in rebase\n>\n> AND sometimes in both cherry-pick and git-revert, depending on the\n> number of commit(s) picked/reverted (see below).\n\nInteresting.  Doesn't \"git cherry-pick A..B\" run post-commit after\neach commit today?  It would be possible to do (B) and retain that\nbehavior.\n\n> Also, even if we go this route, I think the post-commit hook should be\n> added to git-merge whenever it creates a merge commit.\n\nAgreed.\n\n>>     Advantages:\n>>     * makes --am and --merge consistent with minimal user impact\n>\n> ...and also makes --interactive and --exec and other cases consistent too.\n\nHm.  My initial thought with (B) was to stop running post-commit in\nnon-interactive rebase but keep running it in rebase --interactive.\n\nI don't have a strong opinion about this, though.\n\n>>     Disadvantages:\n>>     * meaning of the post-commit hook remains a muddle\n>\n> Why?  \"Commands which create no more than one commit (git-commit,\n> git-merge, maybe single-commit git-revert or git-cherry-pick) call\n> post-commit, commands which create several commits derived from others\n> (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call\n> post-rewrite instead for performance reasons\"\n\nExample: if I run \"git revert A..B\", following that rule we'd want to\ncall post-rewrite instead of post-commit.  But post-rewrite gets input\n\n\t<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF\n\nso that it can be used to copy data such as notes.  What do I pass in as\n<old-sha1>?\n\nRelatedly, if I remember correctly, we don't call post-rewrite in \"git\ncherry-pick\" because cherry-pick is considered to represent a *new*\ncommit instead of a modified version of the old one.  In C++ terms,\nit's a copy constructor instead of a move constructor. ;-)  See\nnotes.rewrite.* in \"git help notes\" for more on that subject.\n\n[...]\n>>  C. stop running the post-commit hook in rebase --merge or commit --amend\n>>\n>>     Advantages:\n>>     * produces a consistent definition of post-commit (\"it's about new\n>>       commits only\")\n>\n> But cherry-pick never creates \"new\" commits if commit --amend doesn't,\n> so we'd also want to turn the post-commit hook off for cherry-pick,\n> right?\n\nWith the copy/modify distinction described above, \"git commit --amend\"\nacts as a \"modify\" operation in a user's workflow, while \"git\ncherry-pick\" acts as a \"copy\" operation.\n\nThat means that with this definition, post-commit is appropriate to\nrun in cherry-pick but not in commit --amend.\n\nThis matches the distinction used by note rewriting and the\npost-rewrite hook.  Looking back at the discussion in\nhttps://lore.kernel.org/git/cover.1266164150.git.trast@student.ethz.ch/\nI don't see this covered, so it's possible I'm remembering wrong (a\npointer to the motivating discussion that inspired that patch series\nwould be welcome).  In the context of note copying, this distinction\nended up being a good decision in practice in most workflows I've\nseen.\n\n[...]\n>>     Disadvantages:\n>>     * the change to historical \"git commit --amend\" behavior is likely\n>>       to be surprising\n>\n> That is certainly one issue, but I'd say this case is riddled with\n> problems and is the most muddled of all.\n\nI'm inclined to agree, based on the \"git commit --amend\" example.\n\n[...]\n> I think it was useful to phrase it this way and list the various\n> advantages, disadvantages, and mitigations; thanks.\n>\n> Is there anything I've missed in my additional details?  Anything\n> sound crazy (beyond what I said I think case C leads to)?\n\nTo sum it up, I lean toward (B), but (A) is not so bad, either\n(especially with the \"I need some time to adapt to the change\" opt-out\nconfig described under mitigations).\n\nThanks for patiently working it through with me.\n\nMy two cents,\nJonathan\n"},{"id":"389955","messageId":"CABPp-BE9XqOCG0VqA5XgDbXm4apw0-_mOqW-JSO+PDZhisO0Rw@mail.gmail.com","threadId":"52496","inReplyTo":"20200116223912.GB71299@google.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-16T23:19:55Z","receivedAt":"2020-01-16T23:20:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jan 16, 2020 at 2:39 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n> [...]\n> >>  B. stop running the post-commit hook in rebase\n> >\n> > AND sometimes in both cherry-pick and git-revert, depending on the\n> > number of commit(s) picked/reverted (see below).\n>\n> Interesting.  Doesn't \"git cherry-pick A..B\" run post-commit after\n> each commit today?  It would be possible to do (B) and retain that\n> behavior.\n\nSure, it'd be possible to retain the post-commit calls for cherry-pick\nbut should we?  Isn't the primary reason the issue came up that having\nbatch-commit operations calling post-commit for each intermediate\ncommit is ugly/unexpected overhead?  If we're ripping it out of rebase\nfor that reason, it seems that cherry-pick should get the same\ntreatment.  I don't want to just move the inconsistencies around, I\nwant to fix them.\n\n> > Also, even if we go this route, I think the post-commit hook should be\n> > added to git-merge whenever it creates a merge commit.\n>\n> Agreed.\n\nYay!\n\n> >>     Advantages:\n> >>     * makes --am and --merge consistent with minimal user impact\n> >\n> > ...and also makes --interactive and --exec and other cases consistent too.\n>\n> Hm.  My initial thought with (B) was to stop running post-commit in\n> non-interactive rebase but keep running it in rebase --interactive.\n>\n> I don't have a strong opinion about this, though.\n\nI'm okay with treating them differently if there's a defining reason\nwhy, but otherwise I think they should all behave the same.  The only\nreasons I've seen for differences all fall in category C so far, which\ngives me a strong opinion against treating them differently.\n\n> >>     Disadvantages:\n> >>     * meaning of the post-commit hook remains a muddle\n> >\n> > Why?  \"Commands which create no more than one commit (git-commit,\n> > git-merge, maybe single-commit git-revert or git-cherry-pick) call\n> > post-commit, commands which create several commits derived from others\n> > (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call\n> > post-rewrite instead for performance reasons\"\n>\n> Example: if I run \"git revert A..B\", following that rule we'd want to\n> call post-rewrite instead of post-commit.  But post-rewrite gets input\n>\n>         <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF\n>\n> so that it can be used to copy data such as notes.  What do I pass in as\n> <old-sha1>?\n>\n> Relatedly, if I remember correctly, we don't call post-rewrite in \"git\n> cherry-pick\" because cherry-pick is considered to represent a *new*\n> commit instead of a modified version of the old one.  In C++ terms,\n> it's a copy constructor instead of a move constructor. ;-)  See\n> notes.rewrite.* in \"git help notes\" for more on that subject.\n\nAh, I see.  Yeah, there's some impedance mismatch between the\npurpose/definition of the post-rewrite hook and what I think is needed\nfor B.  Perhaps create a new post-batch-commit hook\n(\"batch-post-commit\"?) and have git-rebase, git-am, git-cherry-pick,\ngit-revert, etc. call it while git-commit and git-merge call\npost-commit.  (Or maybe even make git-commit and git-merge also call\npost-batch-commit/batch-post-commit but do so with just one commit --\nand just note that post-commit's design of not taking any parameters\nwas a mistake and any place that happens to call it beyond\n\"git-commit/git-merge\" is solely by accident of past implementation\nand is not guaranteed to continue.)\n\nI think the realm of \"copy constructor vs. move constructor\" may make\nsense for post-rewrite-hook, but I'm worried that trying to apply that\nlogic to notification of new commits may lead us into category C\ncraziness.\n\n> [...]\n> >>  C. stop running the post-commit hook in rebase --merge or commit --amend\n> >>\n> >>     Advantages:\n> >>     * produces a consistent definition of post-commit (\"it's about new\n> >>       commits only\")\n> >\n> > But cherry-pick never creates \"new\" commits if commit --amend doesn't,\n> > so we'd also want to turn the post-commit hook off for cherry-pick,\n> > right?\n>\n> With the copy/modify distinction described above, \"git commit --amend\"\n> acts as a \"modify\" operation in a user's workflow, while \"git\n> cherry-pick\" acts as a \"copy\" operation.\n>\n> That means that with this definition, post-commit is appropriate to\n> run in cherry-pick but not in commit --amend.\n\nWhat if there are conflicts?  Because with conflicts, aren't\ncherry-picks more like the 'commit --amend' case since they behave as\na \"modify\"?  Conflict-dependent invoking of the post-commit hook?\n\n> This matches the distinction used by note rewriting and the\n> post-rewrite hook.  Looking back at the discussion in\n> https://lore.kernel.org/git/cover.1266164150.git.trast@student.ethz.ch/\n> I don't see this covered, so it's possible I'm remembering wrong (a\n> pointer to the motivating discussion that inspired that patch series\n> would be welcome).  In the context of note copying, this distinction\n> ended up being a good decision in practice in most workflows I've\n> seen.\n\nYeah, for note copying that distinction makes a lot of sense to me.\nFor \"notification of new commits\", it doesn't seem to jive at all for\nme.\n\n> [...]\n> >>     Disadvantages:\n> >>     * the change to historical \"git commit --amend\" behavior is likely\n> >>       to be surprising\n> >\n> > That is certainly one issue, but I'd say this case is riddled with\n> > problems and is the most muddled of all.\n>\n> I'm inclined to agree, based on the \"git commit --amend\" example.\n>\n> [...]\n> > I think it was useful to phrase it this way and list the various\n> > advantages, disadvantages, and mitigations; thanks.\n> >\n> > Is there anything I've missed in my additional details?  Anything\n> > sound crazy (beyond what I said I think case C leads to)?\n>\n> To sum it up, I lean toward (B), but (A) is not so bad, either\n> (especially with the \"I need some time to adapt to the change\" opt-out\n> config described under mitigations).\n\nSounds like we might be starting to converge towards a similar\nopinion.  I roughly feel the same with leaning towards something like\n(B) _if_ we can address the inconsistencies rather than just shuffle\nthem around (meaning we also turn off post-commit for interactive\nrebase and cherry-pick) and move all of them over to some kind of\nbatch-commit-notification mechanism.\n\n> Thanks for patiently working it through with me.\n>\n> My two cents,\n> Jonathan\n\nLikewise.  :-)\nElijah\n"},{"id":"389956","messageId":"xmqqzhendlvj.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BHiYDLRgPX4A1-8f-zgfsDqf6PkToStaV3ukC5Dsq=3-w@mail.gmail.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-01-16T23:25:52Z","receivedAt":"2020-01-16T23:26:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Why?  \"Commands which create no more than one commit (git-commit,\n> git-merge, maybe single-commit git-revert or git-cherry-pick) call\n> post-commit, commands which create several commits derived from others\n> (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call\n> post-rewrite instead for performance reasons\"\n\nSounds totally wrong.  post-rewrite is about carrying forward data\nthat used to be associated with commit X to new commit Y when Y was\ncreated by \"rewriting\" X (i.e. after Y gets created X no longer has\nany purpose other than as a historical curiosity).  There is nothing\n\"for performance reasons\" here.  There should be nothing for \"git\nrevert\" to do with post-rewrite (because the resulting commit is not\neven a rewrite of the commit that was reverted), and \"git\ncherry-pick\" is not even a rewrite operation (it is to duplicate an\nexisting commit into another unrelated context, without discarding\nthe original commit).\n\nI am a bit confused...\n"},{"id":"389959","messageId":"CABPp-BG2EXNrzs6YM_8-HrKFfSZY4R3LdH0=CS90dWfvPXbYqA@mail.gmail.com","threadId":"52496","inReplyTo":"xmqqzhendlvj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 15/15] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-01-17T00:51:54Z","receivedAt":"2020-01-17T00:52:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jan 16, 2020 at 3:25 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Why?  \"Commands which create no more than one commit (git-commit,\n> > git-merge, maybe single-commit git-revert or git-cherry-pick) call\n> > post-commit, commands which create several commits derived from others\n> > (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call\n> > post-rewrite instead for performance reasons\"\n>\n> Sounds totally wrong.  post-rewrite is about carrying forward data\n> that used to be associated with commit X to new commit Y when Y was\n> created by \"rewriting\" X (i.e. after Y gets created X no longer has\n> any purpose other than as a historical curiosity).  There is nothing\n> \"for performance reasons\" here.  There should be nothing for \"git\n> revert\" to do with post-rewrite (because the resulting commit is not\n> even a rewrite of the commit that was reverted), and \"git\n> cherry-pick\" is not even a rewrite operation (it is to duplicate an\n> existing commit into another unrelated context, without discarding\n> the original commit).\n\nYep, it was totally wrong; Jonathan clued me in later in the thread.\n\n> I am a bit confused...\n\nNo, it was I who was confused.\n"},{"id":"389999","messageId":"47e7c9e6-7d83-185d-792d-f8e084c1a7a1@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/19] rebase: make the default backend configurable","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-17T16:58:31Z","receivedAt":"2020-01-17T16:58:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> This is a re-roll of en/rebase-backend which has been rebased on v2.25.0 and\n> updated to remove the dependence on ra/rebase-i-more-options, and also tries\n> to address feedback from Phillip, Junio, and Jonathan. This series does a\n> lot of work around making the default rebase backend configurable, and\n> switches the default from the am backend to the merge/interactive one.\n> \n> Changes since v3:\n> \n>   * Rebased on origin/master and updated to remove the dependence on\n>     ra/rebase-i-more-options.\n>   * Added two new patches at the start of the series.\n>   * Split the old first patch into two, while modifying them based on\n>     Phillip's feedback (though slightly differently than discussed on the\n>     list; instead of making --keep-empty a synonym for --empty=keep, I\n>     instead kept backward compatibility by making it a no-op).\n>   * I noted the post-commit hook in the differences between backends. Emily\n>     is investigating what changes need to happen there, so I merely\n>     documented the existing differences.\n>   * dropped '-i' from the reflog messages; now they just refer to 'rebase'\n> \n> Changes possibly missing from this version, for discussion:\n> \n>   * I did not remove the --am option as suggested by Phillip, since Junio and\n>     Phillip were still discussing whether it is wanted/needed. >   * I did not address the last two items Jonathan brought up as I \ncouldn't\n>     find enough information to reproduce or understand the problems.\n\nI think I've got a fix for the failure with \n--committer-date-is-author-date that Jonathan reported which I'll post \nnext week - the bug was not in this series, it was just exposed by it. \nI'll try and read through this series next week as well.\n\nBest Wishes\n\nPhillip\n\n> \n> Elijah Newren (19):\n>    git-rebase.txt: update description of --allow-empty-message\n>    t3404: directly test the behavior of interest\n>    rebase (interactive-backend): make --keep-empty the default\n>    rebase (interactive-backend): fix handling of commits that become\n>      empty\n>    t3406: simplify an already simple test\n>    rebase, sequencer: remove the broken GIT_QUIET handling\n>    rebase: make sure to pass along the quiet flag to the sequencer\n>    rebase: fix handling of restrict_revision\n>    t3432: make these tests work with either am or merge backends\n>    rebase: allow more types of rebases to fast-forward\n>    git-rebase.txt: add more details about behavioral differences of\n>      backends\n>    rebase: move incompatibility checks between backend options a bit\n>      earlier\n>    rebase: add an --am option\n>    git-prompt: change the prompt for interactive-based rebases\n>    rebase: drop '-i' from the reflog for interactive-based rebases\n>    rebase tests: mark tests specific to the am-backend with --am\n>    rebase tests: repeat some tests using the merge backend instead of am\n>    rebase: make the backend configurable via config setting\n>    rebase: change the default backend from \"am\" to \"merge\"\n> \n>   Documentation/config/rebase.txt        |   8 ++\n>   Documentation/git-rebase.txt           | 150 +++++++++++++++++---\n>   builtin/rebase.c                       | 186 +++++++++++++++++++------\n>   contrib/completion/git-prompt.sh       |   6 +-\n>   rebase-interactive.c                   |   7 +-\n>   rebase-interactive.h                   |   2 +-\n>   sequencer.c                            |  84 ++++++-----\n>   sequencer.h                            |   3 +-\n>   t/t3400-rebase.sh                      |  36 ++++-\n>   t/t3401-rebase-and-am-rename.sh        |   4 +-\n>   t/t3404-rebase-interactive.sh          |  19 +--\n>   t/t3406-rebase-message.sh              |  19 ++-\n>   t/t3407-rebase-abort.sh                |   6 +-\n>   t/t3420-rebase-autostash.sh            |   2 +-\n>   t/t3421-rebase-topology-linear.sh      |  16 +--\n>   t/t3424-rebase-empty.sh                | 108 ++++++++++++++\n>   t/t3425-rebase-topology-merges.sh      |   8 +-\n>   t/t3427-rebase-subtree.sh              |  12 +-\n>   t/t3432-rebase-fast-forward.sh         |  54 ++++---\n>   t/t5407-post-rewrite-hook.sh           |  12 +-\n>   t/t5520-pull.sh                        |  27 +++-\n>   t/t6047-diff3-conflict-markers.sh      |  13 +-\n>   t/t7512-status-help.sh                 |  12 +-\n>   t/t9106-git-svn-commit-diff-clobber.sh |   3 +-\n>   t/t9903-bash-prompt.sh                 |   8 +-\n>   25 files changed, 595 insertions(+), 210 deletions(-)\n>   create mode 100755 t/t3424-rebase-empty.sh\n> \n> \n> base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v4\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v4\n> Pull-Request: https://github.com/git/git/pull/679\n> \n> Range-diff vs v3:\n> \n>    -:  ---------- >  1:  3ea48d5394 git-rebase.txt: update description of --allow-empty-message\n>    -:  ---------- >  2:  10fdd162a0 t3404: directly test the behavior of interest\n>    1:  1c2b77e94d !  3:  179f82ab83 rebase: extend the options for handling of empty commits\n>       @@ -1,49 +1,46 @@\n>        Author: Elijah Newren <newren@gmail.com>\n>        \n>       -    rebase: extend the options for handling of empty commits\n>       +    rebase (interactive-backend): make --keep-empty the default\n>        \n>       -    Extend the interactive machinery with the ability to handle the full\n>       -    spread of options for how to handle commits that either start or become\n>       -    empty (by \"become empty\" I mean the changes in a commit are a subset of\n>       -    changes that exist upstream, so the net effect of applying the commit is\n>       -    no changes).  Introduce a new command line flag for selecting the\n>       -    desired behavior:\n>       -        --empty={drop,keep,ask}\n>       -    with the definitions:\n>       -        drop: drop empty commits\n>       -        keep: keep empty commits\n>       -        ask:  provide the user a chance to interact and pick what to do with\n>       -              empty commits on a case-by-case basis\n>       +    Different rebase backends have different treatment for commits which\n>       +    start empty (i.e. have no changes relative to their parent), and the\n>       +    --keep-empty option was added at some point to allow adjusting behavior\n>       +    for the interactive backend.  The handling of commits which start empty\n>       +    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n>       +    --allow-empty-message the default, 2018-06-27), which pointed out that\n>       +    the behavior for various backends is often more happenstance than\n>       +    design.  The specific change made in that commit is actually quite\n>       +    relevant as well and much of the logic there directly applies here.\n>        \n>       -    Note that traditionally, am-based rebases have always dropped commits\n>       -    that either started or became empty, while interactive-based rebases\n>       -    have defaulted to ask (and provided an option to keep commits that\n>       -    started empty).  This difference made sense since users of an am-based\n>       -    rebase just wanted to quickly batch apply a sequence of commits, while\n>       -    users editing a todo list will likely want the chance to interact and\n>       -    handle unusual cases on a case-by-case basis.  However, not all rebases\n>       -    using the interactive machinery are explicitly interactive anymore.  In\n>       -    particular --merge was always meant to behave more like --am: just\n>       -    rebase a batch of commits without popping up a todo list.\n>       +    It makes a lot of sense in 'git commit' to error out on the creation of\n>       +    empty commits, unless an override flag is provided.  However, once\n>       +    someone determines that there is a rare case that merits using the\n>       +    manual override to create such a commit, it is somewhere between\n>       +    annoying and harmful to have to take extra steps to keep such\n>       +    intentional commits around.  Granted, empty commits are quite rare,\n>       +    which is why handling of them doesn't get considered much and folks tend\n>       +    to defer to existing (accidental) behavior and assume there was a reason\n>       +    for it, leading them to just add flags (--keep-empty in this case) that\n>       +    allow them to override the bad defaults.  Fix the interactive backend so\n>       +    that --keep-empty is the default, much like we did with\n>       +    --allow-empty-message.  The am backend should also be fixed to have\n>       +    --keep-empty semantics for commits that start empty, but that is not\n>       +    included in this patch other than a testcase documenting the failure.\n>        \n>       -    If the --empty flag is not specified, pick defaults as follows:\n>       -        explicitly interactive: ask\n>       -        --exec: keep (exec is about checking existing commits, and often\n>       -                      used without actually changing the base.  Thus the\n>       -                      expectation is that the user doesn't necessarily want\n>       -                      anything to change; they just want to test).\n>       -        otherwise: drop\n>       -\n>       -    Also, this commit makes --keep-empty just imply --empty=keep, and hides\n>       -    it from help so that we aren't confusing users with different ways to do\n>       -    the same thing.  (I could have added a --drop-empty flag, but then that\n>       -    invites users to specify both --keep-empty and --drop-empty and we have\n>       -    to add sanity checking around that; it seems cleaner to have a single\n>       -    multi-valued option.)  This actually fixes --keep-empty too; previously,\n>       -    it only meant to sometimes keep empty commits, in particular commits\n>       -    which started empty would be kept.  But it would still error out and ask\n>       -    the user what to do with commits that became empty.  Now it keeps empty\n>       -    commits, as instructed.\n>       +    Note that there was one test in t3421 which appears to have been written\n>       +    expecting --keep-empty to not be the default as correct behavior.  This\n>       +    test was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\n>       +    empty commits\", 2013-06-06), which was part of a series focusing on\n>       +    rebase topology and which had an interesting original cover letter at\n>       +    https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\n>       +    which noted\n>       +        Your input especially appreciated on whether you agree with the\n>       +        intent of the test cases.\n>       +    and then went into a long example about how one of the many tests added\n>       +    had several questions about whether it was correct.  As such, I believe\n>       +    most the tests in that series were about testing rebase topology with as\n>       +    many different flags as possible and were not trying to state in general\n>       +    how those flags should behave otherwise.\n>        \n>            Signed-off-by: Elijah Newren <newren@gmail.com>\n>        \n>       @@ -51,126 +48,62 @@\n>         --- a/Documentation/git-rebase.txt\n>         +++ b/Documentation/git-rebase.txt\n>        @@\n>       - \toriginal branch. The index and working tree are also left\n>         \tunchanged as a result.\n>         \n>       -+--empty={drop,keep,ask}::\n>       -+\tHow to handle commits that become empty (because they contain a\n>       -+\tsubset of already upstream changes) or start empty.  With drop\n>       -+\t(the default), commits that start or become empty are dropped.\n>       -+\tWith keep (implied by --exec), such commits are kept.  With ask\n>       -+\t(implied by --interactive), the rebase will halt when an empty\n>       -+\tcommit is applied allowing you to choose whether to drop it or\n>       -+\tcommit it.  Also with ask, if the rebase is interactive then\n>       -+\tcommits which start empty will be commented out in the todo\n>       -+\taction list (giving you a chance to uncomment).\n>       -++\n>       -+Note that this has no effect on commits which are already upstream (as\n>       -+can be checked via `git log --cherry-mark ...`), which are always\n>       -+dropped by rebase.\n>       -++\n>       -+See also INCOMPATIBLE OPTIONS below.\n>       -+\n>         --keep-empty::\n>        -\tKeep the commits that do not change anything from its\n>        -\tparents in the result.\n>       -+\tDeprecated alias for what is now known as --empty=keep.\n>       ++\tNo-op.  Rebasing commits that started empty (had no change\n>       ++\trelative to their parent) used to fail and this option would\n>       ++\toverride that behavior, allowing commits with empty changes to\n>       ++\tbe rebased.  Now commits with no changes do not cause rebasing\n>       ++\tto halt.\n>         +\n>       - See also INCOMPATIBLE OPTIONS below.\n>       -\n>       -@@\n>       -  * --interactive\n>       -  * --exec\n>       -  * --keep-empty\n>       -+ * --empty=\n>       -  * --edit-todo\n>       -  * --root when used in combination with --onto\n>       -\n>       -@@\n>       -  * --preserve-merges and --ignore-whitespace\n>       -  * --preserve-merges and --committer-date-is-author-date\n>       -  * --preserve-merges and --ignore-date\n>       -+ * --preserve-merges and --empty=\n>       -  * --keep-base and --onto\n>       -  * --keep-base and --root\n>       +-See also INCOMPATIBLE OPTIONS below.\n>       ++See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n>         \n>       + --allow-empty-message::\n>       + \tNo-op.  Rebasing commits with an empty message used to fail\n>        @@\n>       + Empty commits\n>       + ~~~~~~~~~~~~~\n>         \n>       - There are some subtle differences how the backends behave.\n>       -\n>       --Empty commits\n>       --~~~~~~~~~~~~~\n>       --\n>        -The am backend drops any \"empty\" commits, regardless of whether the\n>        -commit started empty (had no changes relative to its parent to\n>        -start with) or ended empty (all changes were already applied\n>        -upstream in other commits).\n>       --\n>       ++The am backend unfortunately drops intentionally empty commits, i.e.\n>       ++commits that started empty, though these are rare in practice.  It\n>       ++also drops commits that become empty and has no option for controlling\n>       ++this behavior.\n>       +\n>        -The interactive backend drops commits by default that\n>        -started empty and halts if it hits a commit that ended up empty.\n>        -The `--keep-empty` option exists for the interactive backend to allow\n>        -it to keep commits that started empty.\n>       --\n>       ++The interactive backend keeps intentionally empty commits.\n>       ++Unfortunately, it always halts whenever it runs across a commit that\n>       ++becomes empty, even when the rebase is not explicitly interactive.\n>       +\n>         Directory rename detection\n>         ~~~~~~~~~~~~~~~~~~~~~~~~~~\n>       -\n>        \n>         diff --git a/builtin/rebase.c b/builtin/rebase.c\n>         --- a/builtin/rebase.c\n>         +++ b/builtin/rebase.c\n>       -@@\n>       - \tREBASE_PRESERVE_MERGES\n>       - };\n>       -\n>       -+enum empty_type {\n>       -+\tEMPTY_UNSPECIFIED = -1,\n>       -+\tEMPTY_DROP,\n>       -+\tEMPTY_KEEP,\n>       -+\tEMPTY_ASK\n>       -+};\n>       -+\n>       - struct rebase_options {\n>       - \tenum rebase_type type;\n>       -+\tenum empty_type empty;\n>       - \tconst char *state_dir;\n>       - \tstruct commit *upstream;\n>       - \tconst char *upstream_name;\n>        @@\n>         \tconst char *action;\n>         \tint signoff;\n>         \tint allow_rerere_autoupdate;\n>        -\tint keep_empty;\n>         \tint autosquash;\n>       - \tint ignore_whitespace;\n>         \tchar *gpg_sign_opt;\n>       -@@\n>       -\n>       - #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n>       - \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n>       -+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n>       - \t\t.flags = REBASE_NO_QUIET, \t\t\\\n>       - \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n>       - \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n>       -@@\n>       - \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n>       - \treplay.allow_empty = 1;\n>       - \treplay.allow_empty_message = opts->allow_empty_message;\n>       -+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n>       -+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n>       -+\treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n>       -+\t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n>       - \treplay.verbose = opts->flags & REBASE_VERBOSE;\n>       - \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n>       - \treplay.committer_date_is_author_date =\n>       + \tint autostash;\n>        @@\n>         \n>         \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n>         \n>        -\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n>       -+\tflags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;\n>       -+\tflags |= (opts->empty == EMPTY_ASK &&\n>       -+\t\t  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?\n>       -+\t\t\tTODO_LIST_ASK_EMPTY : 0;\n>         \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n>         \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n>         \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n>       @@ -183,10 +116,8 @@\n>        +{\n>        +\tstruct rebase_options *opts = opt->value;\n>        +\n>       -+\tBUG_ON_OPT_NEG(unset);\n>        +\tBUG_ON_OPT_ARG(arg);\n>        +\n>       -+\topts->empty = EMPTY_KEEP;\n>        +\topts->type = REBASE_INTERACTIVE;\n>        +\treturn 0;\n>        +}\n>       @@ -201,62 +132,28 @@\n>        -\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n>        +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>        +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n>       -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n>       ++\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n>        +\t\t\tparse_opt_keep_empty },\n>       - \t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n>       - \t\t\t N_(\"allow commits with empty messages\")),\n>       - \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n>       + \t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n>       + \t\t\t   N_(\"allow commits with empty messages\"),\n>       + \t\t\t   PARSE_OPT_HIDDEN),\n>        @@\n>         \t\topts->allow_rerere_autoupdate ?\n>         \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n>         \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n>        -\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n>       -+\tadd_var(&script_snippet, \"empty\", opts->empty == EMPTY_KEEP ? \"yes\" : \"\");\n>         \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n>         \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n>         \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n>       -@@\n>       - \treturn 0;\n>       - }\n>       -\n>       -+static enum empty_type parse_empty_value(const char *value)\n>       -+{\n>       -+\tif (!strcasecmp(value, \"drop\"))\n>       -+\t\treturn EMPTY_DROP;\n>       -+\telse if (!strcasecmp(value, \"keep\"))\n>       -+\t\treturn EMPTY_KEEP;\n>       -+\telse if (!strcasecmp(value, \"ask\"))\n>       -+\t\treturn EMPTY_ASK;\n>       -+\n>       -+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n>       -+}\n>       -+\n>       -+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n>       -+{\n>       -+\tstruct rebase_options *options = opt->value;\n>       -+\tenum empty_type value = parse_empty_value(arg);\n>       -+\n>       -+\tBUG_ON_OPT_NEG(unset);\n>       -+\n>       -+\toptions->empty = value;\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>        @@\n>         \t\t\t\t \"ignoring them\"),\n>         \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>         \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n>        -\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n>        -\t\t\t N_(\"preserve empty commits during rebase\")),\n>       -+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n>       -+\t\t\t       N_(\"how to handle empty commits\"),\n>       -+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n>        +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>        +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n>       -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,\n>       ++\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n>        +\t\t\tparse_opt_keep_empty },\n>         \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n>         \t\t\t N_(\"move commits that begin with \"\n>       @@ -267,26 +164,10 @@\n>         \n>        -\tif (options.keep_empty)\n>        -\t\timply_interactive(&options, \"--keep-empty\");\n>       -+\tif (options.empty != EMPTY_UNSPECIFIED)\n>       -+\t\timply_interactive(&options, \"--empty\");\n>       -\n>       +-\n>         \tif (gpg_sign) {\n>         \t\tfree(options.gpg_sign_opt);\n>       -@@\n>       - \t\tbreak;\n>       - \t}\n>       -\n>       -+\tif (options.empty == EMPTY_UNSPECIFIED) {\n>       -+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n>       -+\t\t\toptions.empty = EMPTY_ASK;\n>       -+\t\telse if (exec.nr > 0)\n>       -+\t\t\toptions.empty = EMPTY_KEEP;\n>       -+\t\telse\n>       -+\t\t\toptions.empty = EMPTY_DROP;\n>       -+\t}\n>       - \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n>       - \t\tdie(_(\"--reschedule-failed-exec requires \"\n>       - \t\t      \"--exec or --interactive\"));\n>       + \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n>        \n>         diff --git a/rebase-interactive.c b/rebase-interactive.c\n>         --- a/rebase-interactive.c\n>       @@ -296,19 +177,22 @@\n>         }\n>         \n>        -void append_todo_help(unsigned keep_empty, int command_count,\n>       -+void append_todo_help(unsigned no_ask_empty, int command_count,\n>       ++void append_todo_help(int command_count,\n>         \t\t      const char *shortrevisions, const char *shortonto,\n>         \t\t      struct strbuf *buf)\n>         {\n>        @@\n>       + \t\t\t\"the rebase will be aborted.\\n\\n\");\n>         \n>         \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n>       -\n>       +-\n>        -\tif (!keep_empty) {\n>       -+\tif (!no_ask_empty) {\n>       - \t\tmsg = _(\"Note that empty commits are commented out\");\n>       - \t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n>       - \t}\n>       +-\t\tmsg = _(\"Note that empty commits are commented out\");\n>       +-\t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n>       +-\t}\n>       + }\n>       +\n>       + int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>        \n>         diff --git a/rebase-interactive.h b/rebase-interactive.h\n>         --- a/rebase-interactive.h\n>       @@ -318,7 +202,7 @@\n>         struct todo_list;\n>         \n>        -void append_todo_help(unsigned keep_empty, int command_count,\n>       -+void append_todo_help(unsigned no_ask_empty, int command_count,\n>       ++void append_todo_help(int command_count,\n>         \t\t      const char *shortrevisions, const char *shortonto,\n>         \t\t      struct strbuf *buf);\n>         int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>       @@ -327,157 +211,51 @@\n>         --- a/sequencer.c\n>         +++ b/sequencer.c\n>        @@\n>       - static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n>       - static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n>       - static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n>       -+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n>       -+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n>       -+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, \"rebase-merge/ask_on_initially_empty\")\n>       -\n>       - static int git_sequencer_config(const char *k, const char *v, void *cb)\n>       + \t\t       struct replay_opts *opts,\n>       + \t\t       struct commit *commit)\n>         {\n>       +-\tint index_unchanged, empty_commit;\n>       ++\tint index_unchanged, originally_empty;\n>       +\n>       + \t/*\n>       + \t * Three cases:\n>        @@\n>       - \tempty_commit = is_original_commit_empty(commit);\n>       - \tif (empty_commit < 0)\n>       - \t\treturn empty_commit;\n>       + \tif (opts->keep_redundant_commits)\n>       + \t\treturn 1;\n>       +\n>       +-\tempty_commit = is_original_commit_empty(commit);\n>       +-\tif (empty_commit < 0)\n>       +-\t\treturn empty_commit;\n>        -\tif (!empty_commit)\n>       -+\tif (!empty_commit || opts->ask_on_initially_empty)\n>       ++\toriginally_empty = is_original_commit_empty(commit);\n>       ++\tif (originally_empty < 0)\n>       ++\t\treturn originally_empty;\n>       ++\tif (!originally_empty)\n>         \t\treturn 0;\n>         \telse\n>         \t\treturn 1;\n>       -@@\n>       - \tchar *author = NULL;\n>       - \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n>       - \tstruct strbuf msgbuf = STRBUF_INIT;\n>       --\tint res, unborn = 0, reword = 0, allow;\n>       -+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n>       -\n>       - \tif (opts->no_commit) {\n>       - \t\t/*\n>       -@@\n>       - \t\tgoto leave;\n>       - \t}\n>       -\n>       --\tallow = allow_empty(r, opts, commit);\n>       --\tif (allow < 0) {\n>       --\t\tres = allow;\n>       --\t\tgoto leave;\n>       --\t} else if (allow)\n>       --\t\tflags |= ALLOW_EMPTY;\n>       --\tif (!opts->no_commit) {\n>       -+\tdrop_commit = 0;\n>       -+\tif (opts->drop_redundant_commits && is_index_unchanged(r)) {\n>       -+\t\tdrop_commit = 1;\n>       -+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\"));\n>       -+\t} else {\n>       -+\t\tallow = allow_empty(r, opts, commit);\n>       -+\t\tif (allow < 0) {\n>       -+\t\t\tres = allow;\n>       -+\t\t\tgoto leave;\n>       -+\t\t} else if (allow) {\n>       -+\t\t\tflags |= ALLOW_EMPTY;\n>       -+\t\t}\n>       -+\t}\n>       -+\tif (!opts->no_commit && !drop_commit) {\n>       - \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>       - \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n>       - \t\telse\n>       -@@\n>       - \telse if (!strcmp(key, \"options.allow-empty-message\"))\n>       - \t\topts->allow_empty_message =\n>       - \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n>       -+\telse if (!strcmp(key, \"options.drop-redundant-commits\"))\n>       -+\t\topts->drop_redundant_commits =\n>       -+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n>       - \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n>       - \t\topts->keep_redundant_commits =\n>       - \t\t\tgit_config_bool_or_int(key, value, &error_flag);\n>       -+\telse if (!strcmp(key, \"options.ask_on_initially_empty\"))\n>       -+\t\topts->ask_on_initially_empty =\n>       -+\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n>       - \telse if (!strcmp(key, \"options.signoff\"))\n>       - \t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n>       - \telse if (!strcmp(key, \"options.record-origin\"))\n>       -@@\n>       - \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n>       - \t\t\topts->reschedule_failed_exec = 1;\n>       -\n>       -+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n>       -+\t\t\topts->drop_redundant_commits = 1;\n>       -+\n>       -+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n>       -+\t\t\topts->keep_redundant_commits = 1;\n>       -+\n>       -+\t\tif (file_exists(rebase_path_ask_on_initially_empty()))\n>       -+\t\t\topts->ask_on_initially_empty = 1;\n>       -+\n>       - \t\tread_strategy_opts(opts, &buf);\n>       - \t\tstrbuf_release(&buf);\n>       -\n>       -@@\n>       - \t\twrite_file(rebase_path_cdate_is_adate(), \"%s\", \"\");\n>       - \tif (opts->ignore_date)\n>       - \t\twrite_file(rebase_path_ignore_date(), \"%s\", \"\");\n>       -+\tif (opts->drop_redundant_commits)\n>       -+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n>       -+\tif (opts->keep_redundant_commits)\n>       -+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n>       -+\tif (opts->ask_on_initially_empty)\n>       -+\t\twrite_file(rebase_path_ask_on_initially_empty(), \"%s\", \"\");\n>       - \tif (opts->reschedule_failed_exec)\n>       - \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n>       -\n>       -@@\n>       - \tif (opts->allow_empty_message)\n>       - \t\tres |= git_config_set_in_file_gently(opts_file,\n>       - \t\t\t\t\"options.allow-empty-message\", \"true\");\n>       -+\tif (opts->drop_redundant_commits)\n>       -+\t\tres |= git_config_set_in_file_gently(opts_file,\n>       -+\t\t\t\t\"options.drop-redundant-commits\", \"true\");\n>       - \tif (opts->keep_redundant_commits)\n>       - \t\tres |= git_config_set_in_file_gently(opts_file,\n>       - \t\t\t\t\"options.keep-redundant-commits\", \"true\");\n>       -+\tif (opts->ask_on_initially_empty)\n>       -+\t\tres |= git_config_set_in_file_gently(opts_file,\n>       -+\t\t\t\t\"options.ask_on_initially_empty\", \"true\");\n>       - \tif (opts->signoff)\n>       - \t\tres |= git_config_set_in_file_gently(opts_file,\n>       - \t\t\t\t\t\"options.signoff\", \"true\");\n>        @@\n>         \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n>         \t\t\t\t   unsigned flags)\n>         {\n>        -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>       -+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n>       -+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n>         \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n>         \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n>         \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n>       -@@\n>       - \t\tis_empty = is_original_commit_empty(commit);\n>       - \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n>       - \t\t\tcontinue;\n>       -+\t\tif (is_empty && drop_empty)\n>       -+\t\t\tcontinue;\n>       -\n>       - \t\tstrbuf_reset(&oneline);\n>       - \t\tpretty_print_commit(pp, commit, &oneline);\n>        @@\n>         \t\tif (!to_merge) {\n>         \t\t\t/* non-merge commit: easy case */\n>         \t\t\tstrbuf_reset(&buf);\n>        -\t\t\tif (!keep_empty && is_empty)\n>       -+\t\t\tif (is_empty && ask_empty)\n>       - \t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n>       +-\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n>         \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n>         \t\t\t\t    oid_to_hex(&commit->object.oid),\n>       + \t\t\t\t    oneline.buf);\n>        @@\n>         \tstruct pretty_print_context pp = {0};\n>         \tstruct rev_info revs;\n>         \tstruct commit *commit;\n>        -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>       -+\tint drop_empty = flags & TODO_LIST_DROP_EMPTY;\n>       -+\tint ask_empty = flags & TODO_LIST_ASK_EMPTY;\n>         \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n>         \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n>         \n>       @@ -491,19 +269,16 @@\n>         \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n>         \t\t\tcontinue;\n>        -\t\tif (!keep_empty && is_empty)\n>       -+\t\tif (is_empty && drop_empty)\n>       -+\t\t\tcontinue;\n>       -+\t\tif (is_empty && ask_empty)\n>       - \t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n>       +-\t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n>         \t\tstrbuf_addf(out, \"%s %s \", insn,\n>         \t\t\t    oid_to_hex(&commit->object.oid));\n>       + \t\tpretty_print_commit(&pp, commit, out);\n>        @@\n>         \n>         \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n>         \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n>        -\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n>       -+\t\tappend_todo_help(!(flags & TODO_LIST_ASK_EMPTY),\n>       -+\t\t\t\t count_commands(todo_list),\n>       ++\t\tappend_todo_help(count_commands(todo_list),\n>         \t\t\t\t shortrevisions, shortonto, &buf);\n>         \n>         \tres = write_message(buf.buf, buf.len, file, 0);\n>       @@ -511,16 +286,6 @@\n>         diff --git a/sequencer.h b/sequencer.h\n>         --- a/sequencer.h\n>         +++ b/sequencer.h\n>       -@@\n>       - \tint allow_rerere_auto;\n>       - \tint allow_empty;\n>       - \tint allow_empty_message;\n>       -+\tint drop_redundant_commits;\n>       - \tint keep_redundant_commits;\n>       -+\tint ask_on_initially_empty;\n>       - \tint verbose;\n>       - \tint quiet;\n>       - \tint reschedule_failed_exec;\n>        @@\n>         int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n>         int sequencer_remove_state(struct replay_opts *opts);\n>       @@ -530,19 +295,34 @@\n>         #define TODO_LIST_SHORTEN_IDS (1U << 1)\n>         #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n>         #define TODO_LIST_REBASE_MERGES (1U << 3)\n>       -@@\n>       -  * `--onto`, we do not want to re-generate the root commits.\n>       -  */\n>       - #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)\n>       -+#define TODO_LIST_DROP_EMPTY (1U << 7)\n>       -+#define TODO_LIST_ASK_EMPTY (1U << 8)\n>       -\n>       -\n>       - int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n>        \n>         diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n>         --- a/t/t3421-rebase-topology-linear.sh\n>         +++ b/t/t3421-rebase-topology-linear.sh\n>       +@@\n>       + test_run_rebase () {\n>       + \tresult=$1\n>       + \tshift\n>       +-\ttest_expect_$result \"rebase $* drops empty commit\" \"\n>       ++\ttest_expect_$result \"rebase $* keeps begin-empty commits\" \"\n>       + \t\treset_rebase &&\n>       +-\t\tgit rebase $* c l &&\n>       +-\t\ttest_cmp_rev c HEAD~2 &&\n>       +-\t\ttest_linear_range 'd l' c..\n>       ++\t\tgit rebase $* j l &&\n>       ++\t\ttest_cmp_rev c HEAD~4 &&\n>       ++\t\ttest_linear_range 'j d k l' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase failure ''\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       +-test_have_prereq !REBASE_P || test_run_rebase success -p\n>       ++test_have_prereq !REBASE_P || test_run_rebase failure -p\n>       +\n>       + test_run_rebase () {\n>       + \tresult=$1\n>        @@\n>         test_run_rebase success ''\n>         test_run_rebase success -m\n>       @@ -603,31 +383,22 @@\n>        +\tgit commit -m \"Five letters ought to be enough for anybody\"\n>        +'\n>        +\n>       -+test_expect_success 'rebase --merge --empty=drop' '\n>       ++test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n>       ++\ttest_when_finished \"git rebase --abort\" &&\n>        +\tgit checkout -B testing localmods &&\n>       -+\tgit rebase --merge --empty=drop upstream &&\n>       -+\n>       -+\ttest_write_lines C B A >expect &&\n>       -+\tgit log --format=%s >actual &&\n>       -+\ttest_cmp expect actual\n>       -+'\n>       ++\t# rebase (--am) should not drop commits that start empty\n>       ++\tgit rebase upstream &&\n>        +\n>       -+test_expect_success 'rebase --merge --empty=keep' '\n>       -+\tgit checkout -B testing localmods &&\n>       -+\tgit rebase --merge --empty=keep upstream &&\n>       -+\n>       -+\ttest_write_lines D C2 C B A >expect &&\n>       ++\ttest_write_lines D C B A >expect &&\n>        +\tgit log --format=%s >actual &&\n>        +\ttest_cmp expect actual\n>        +'\n>        +\n>       -+test_expect_success 'rebase --merge --empty=ask' '\n>       ++test_expect_failure 'rebase --merge with a variety of empty commits' '\n>       ++\ttest_when_finished \"git rebase --abort\" &&\n>        +\tgit checkout -B testing localmods &&\n>       -+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n>       -+\n>       -+\ttest_must_fail git rebase --skip &&\n>       -+\tgit commit --allow-empty &&\n>       -+\tgit rebase --continue &&\n>       ++\t# rebase --merge should not halt on the commit that becomes empty\n>       ++\tgit rebase --merge upstream &&\n>        +\n>        +\ttest_write_lines D C B A >expect &&\n>        +\tgit log --format=%s >actual &&\n>       @@ -636,25 +407,17 @@\n>        +\n>        +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>        +\n>       -+test_expect_success 'rebase --interactive --empty=drop' '\n>       ++test_expect_success 'rebase --interactive with a variety of empty commits' '\n>        +\tgit checkout -B testing localmods &&\n>       -+\tgit rebase --interactive --empty=drop upstream &&\n>       ++\ttest_must_fail git rebase --interactive upstream &&\n>        +\n>       -+\ttest_write_lines C B A >expect &&\n>       -+\tgit log --format=%s >actual &&\n>       -+\ttest_cmp expect actual\n>       -+'\n>       -+\n>       -+test_expect_success 'rebase --interactive --empty=keep' '\n>       -+\tgit checkout -B testing localmods &&\n>       -+\tgit rebase --interactive --empty=keep upstream &&\n>       ++\tgit rebase --skip &&\n>        +\n>       -+\ttest_write_lines D C2 C B A >expect &&\n>       ++\ttest_write_lines D C B A >expect &&\n>        +\tgit log --format=%s >actual &&\n>        +\ttest_cmp expect actual\n>        +'\n>        +\n>       -+\n>        +test_done\n>        \n>         diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n>       @@ -665,32 +428,28 @@\n>         '\n>         \n>        -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n>       -+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n>       ++test_expect_success 'Rebase -Xsubtree --onto commit' '\n>         \treset_rebase &&\n>         \tgit checkout -b rebase-onto to-rebase &&\n>        -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n>       -+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n>       ++\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n>         \t: first pick results in no changes &&\n>        -\tgit rebase --continue &&\n>       -+\ttest_must_fail git rebase --skip &&\n>       -+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n>       -+\tgit commit --allow-empty &&\n>       ++\tgit rebase --skip &&\n>         \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>         \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>         \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>         '\n>         \n>        -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n>       -+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n>       ++test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n>         \treset_rebase &&\n>         \tgit checkout -b rebase-merges-onto to-rebase &&\n>        -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n>       -+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n>       ++\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n>         \t: first pick results in no changes &&\n>        -\tgit rebase --continue &&\n>       -+\ttest_must_fail git rebase --skip &&\n>       -+\t: last pick was an empty commit that has no changes, but we want to keep it &&\n>       -+\tgit commit --allow-empty &&\n>       ++\tgit rebase --skip &&\n>         \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>         \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>         \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>    -:  ---------- >  4:  c9542a2abe rebase (interactive-backend): fix handling of commits that become empty\n>    2:  bd3c5ec155 =  5:  9f66229d5c t3406: simplify an already simple test\n>    3:  49388b79fd =  6:  8d731fa39c rebase, sequencer: remove the broken GIT_QUIET handling\n>    4:  478479358f !  7:  b6b6597eef rebase: make sure to pass along the quiet flag to the sequencer\n>       @@ -8,13 +8,13 @@\n>         --- a/builtin/rebase.c\n>         +++ b/builtin/rebase.c\n>        @@\n>       + \treplay.allow_empty_message = opts->allow_empty_message;\n>       + \treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n>         \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n>       - \treplay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&\n>       - \t\t\t\t\t !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));\n>        +\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n>         \treplay.verbose = opts->flags & REBASE_VERBOSE;\n>         \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n>       - \treplay.committer_date_is_author_date =\n>       + \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n>        @@\n>         \t\t\t N_(\"allow pre-rebase hook to run\")),\n>         \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n>    5:  ee26f5a161 =  8:  0acefa988b rebase: fix handling of restrict_revision\n>    6:  34a69def33 =  9:  8c5b5b5133 t3432: make these tests work with either am or merge backends\n>    7:  f2c92853b4 ! 10:  b8c087d6fb rebase: allow more types of rebases to fast-forward\n>       @@ -35,8 +35,8 @@\n>         \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>         \t\t\t   N_(\"revision\"),\n>        @@\n>       - \t    options.ignore_date)\n>       - \t\toptions.flags |= REBASE_FORCE;\n>       + \t\t    state_dir_base, cmd_live_rebase, buf.buf);\n>       + \t}\n>         \n>        +\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n>        +\t    (action != ACTION_NONE) ||\n>       @@ -47,7 +47,9 @@\n>        +\n>         \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n>         \t\tconst char *option = options.git_am_opts.argv[i], *p;\n>       - \t\tif (!strcmp(option, \"--whitespace=fix\") ||\n>       + \t\tif (!strcmp(option, \"--committer-date-is-author-date\") ||\n>       + \t\t    !strcmp(option, \"--ignore-date\") ||\n>       + \t\t    !strcmp(option, \"--whitespace=fix\") ||\n>         \t\t    !strcmp(option, \"--whitespace=strip\"))\n>        -\t\t\toptions.flags |= REBASE_FORCE;\n>        +\t\t\tallow_preemptive_ff = 0;\n>    8:  b307340f7c ! 11:  b50a1741e0 git-rebase.txt: add more details about behavioral differences of backends\n>       @@ -8,23 +8,24 @@\n>         --- a/Documentation/git-rebase.txt\n>         +++ b/Documentation/git-rebase.txt\n>        @@\n>       - with `--keep-base` in order to drop those commits from your branch.\n>         \n>         --ignore-whitespace::\n>       --\tBehaves differently depending on which backend is selected.\n>       --+\n>       --'am' backend: When applying a patch, ignore changes in whitespace in\n>       --context lines if necessary.\n>       --+\n>       --'interactive' backend: Treat lines with only whitespace changes as\n>       --unchanged for the sake of a three-way merge.\n>       -+\tIgnore whitespace-only changes in the commits being rebased,\n>       -+\twhich may avoid \"unnecessary\" conflicts.  (Both backends\n>       -+\tcurrently have differing edgecase bugs with this option; see\n>       -+\tBEHAVIORAL DIFFERENCES.)\n>       -\n>         --whitespace=<option>::\n>       - \tThis flag is passed to the 'git apply' program\n>       +-\tThese flag are passed to the 'git apply' program\n>       ++\tThese flags are passed to the 'git apply' program\n>       + \t(see linkgit:git-apply[1]) that applies the patch.\n>       + +\n>       + See also INCOMPATIBLE OPTIONS below.\n>       +@@\n>       +\n>       +  * --committer-date-is-author-date\n>       +  * --ignore-date\n>       +- * --whitespace\n>       +  * --ignore-whitespace\n>       ++ * --whitespace\n>       +  * -C\n>       +\n>       + are incompatible with the following options:\n>        @@\n>         Directory rename detection\n>         ~~~~~~~~~~~~~~~~~~~~~~~~~~\n>       @@ -82,24 +83,14 @@\n>        +The interactive backend works with the full commits on both sides of\n>        +history and thus has no such limitations.\n>        +\n>       -+--ignore-whitespace\n>       -+~~~~~~~~~~~~~~~~~~~\n>       ++Hooks\n>       ++~~~~~\n>        +\n>       -+The --ignore-whitespace option is supposed to ignore whitespace-only\n>       -+changes if it allows the code to merge cleanly.  Unfortunately, the\n>       -+different backends implement this differently, and both have different\n>       -+edge case bugs.\n>       -++\n>       -+'am' backend: When applying a patch, ignore changes in whitespace in\n>       -+context lines if necessary.  (Which implies that if the whitespace\n>       -+change was not in the context lines but on a line with a real change,\n>       -+then the rebase will still fail with \"unnecessary\" content conflicts.)\n>       -++\n>       -+'interactive' backend: Treat lines with only whitespace changes as\n>       -+unchanged for the sake of a three-way merge.  This means that if one\n>       -+side made no changes and the commits being rebased had whitespace-only\n>       -+changes, those whitespaces fixups will be discarded despite the fact\n>       -+that they present no content conflict.\n>       ++The am backend has not traditionally called the post-commit hook,\n>       ++while the merge/interactive backend has.  However, this was by\n>       ++accident of implementation rather than by design.  Both backends\n>       ++should have the same behavior, though it is not clear which one is\n>       ++correct.\n>        +\n>        +Miscellaneous differences\n>        +~~~~~~~~~~~~~~~~~~~~~~~~~\n>       @@ -123,19 +114,3 @@\n>         \n>         include::merge-strategies.txt[]\n>         \n>       -\n>       - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\n>       - --- a/t/t3433-rebase-options-compatibility.sh\n>       - +++ b/t/t3433-rebase-options-compatibility.sh\n>       -@@\n>       - GIT_AUTHOR_DATE=\"1999-04-02T08:03:20+05:30\"\n>       - export GIT_AUTHOR_DATE\n>       -\n>       --# This is a special case in which both am and interactive backends\n>       --# provide the same output. It was done intentionally because\n>       --# both the backends fall short of optimal behaviour.\n>       -+# This is a common case in which both am and interactive backends\n>       -+# provide the same output with --ignore-whitespace.\n>       - test_expect_success 'setup' '\n>       - \tgit checkout -b topic &&\n>       - \tq_to_tab >file <<-\\EOF &&\n>    9:  7c3f2e07f3 = 12:  58e6e4ffb3 rebase: move incompatibility checks between backend options a bit earlier\n>   10:  1df11f0b51 ! 13:  5478c730ac rebase: add an --am option\n>       @@ -24,8 +24,8 @@\n>        +See also INCOMPATIBLE OPTIONS below.\n>        +\n>         --empty={drop,keep,ask}::\n>       - \tHow to handle commits that become empty (because they contain a\n>       - \tsubset of already upstream changes) or start empty.  With drop\n>       + \tHow to handle commits that are not empty to start and are not\n>       + \tclean cherry-picks of any upstream commit, but which become\n>        @@\n>         \tEnsure at least <n> lines of surrounding context match before\n>         \tand after each change.  When fewer lines of surrounding\n>       @@ -37,7 +37,7 @@\n>         \n>        @@\n>         --whitespace=<option>::\n>       - \tThis flag is passed to the 'git apply' program\n>       + \tThese flags are passed to the 'git apply' program\n>         \t(see linkgit:git-apply[1]) that applies the patch.\n>        +\tImplies --am.\n>         +\n>       @@ -48,9 +48,9 @@\n>         The following options:\n>         \n>        + * --am\n>       -  * --whitespace\n>       -  * -C\n>       -\n>       +  * --committer-date-is-author-date\n>       +  * --ignore-date\n>       +  * --ignore-whitespace\n>        \n>         diff --git a/builtin/rebase.c b/builtin/rebase.c\n>         --- a/builtin/rebase.c\n>   11:  ff43593211 = 14:  db5e29bd81 git-prompt: change the prompt for interactive-based rebases\n>    -:  ---------- > 15:  413e190ac9 rebase: drop '-i' from the reflog for interactive-based rebases\n>   12:  99388f24e5 ! 16:  170be283a8 rebase tests: mark tests specific to the am-backend with --am\n>       @@ -228,40 +228,6 @@\n>         \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n>         }\n>        \n>       - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh\n>       - --- a/t/t3433-rebase-options-compatibility.sh\n>       - +++ b/t/t3433-rebase-options-compatibility.sh\n>       -@@\n>       - \tnew line 2\n>       - \tline 3\n>       - \tEOF\n>       --\ttest_must_fail git rebase main side &&\n>       -+\ttest_must_fail git rebase --am main side &&\n>       - \tgit rebase --abort &&\n>       --\tgit rebase --ignore-whitespace main side &&\n>       -+\tgit rebase --am --ignore-whitespace main side &&\n>       - \ttest_cmp expect file\n>       - '\n>       -\n>       -@@\n>       -\n>       - test_expect_success '--committer-date-is-author-date works with am backend' '\n>       - \tgit commit --amend &&\n>       --\tgit rebase --committer-date-is-author-date HEAD^ &&\n>       -+\tgit rebase --am --committer-date-is-author-date HEAD^ &&\n>       - \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n>       - \tgit show HEAD --pretty=\"format:%ci\" >committertime &&\n>       - \ttest_cmp authortime committertime\n>       -@@\n>       - # sets to +0530.\n>       - test_expect_success '--ignore-date works with am backend' '\n>       - \tgit commit --amend --date=\"$GIT_AUTHOR_DATE\" &&\n>       --\tgit rebase --ignore-date HEAD^ &&\n>       -+\tgit rebase --am --ignore-date HEAD^ &&\n>       - \tgit show HEAD --pretty=\"format:%ai\" >authortime &&\n>       - \tgrep \"+0000\" authortime\n>       - '\n>       -\n>         diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\n>         --- a/t/t5407-post-rewrite-hook.sh\n>         +++ b/t/t5407-post-rewrite-hook.sh\n>   13:  c2ba6317bf = 17:  1e3d4066c4 rebase tests: repeat some tests using the merge backend instead of am\n>   14:  8bec6df51a = 18:  9b4ac83d2d rebase: make the backend configurable via config setting\n>   15:  044853fd61 = 19:  859a4a94d7 rebase: change the default backend from \"am\" to \"merge\"\n> \n"},{"id":"391195","messageId":"xmqqa75w68wd.fsf@gitster-ct.c.googlers.com","threadId":"52496","inReplyTo":"47e7c9e6-7d83-185d-792d-f8e084c1a7a1@gmail.com","subject":"Re: [PATCH v4 00/19] rebase: make the default backend configurable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-05T21:06:58Z","receivedAt":"2020-02-05T21:07:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Changes possibly missing from this version, for discussion:\n>>\n>>   * I did not remove the --am option as suggested by Phillip, since Junio and\n>>     Phillip were still discussing whether it is wanted/needed.\n>>   * I did not address the last two items Jonathan brought up as I \n>>     couldn't find enough information to reproduce or understand the problems.\n>\n> I think I've got a fix for the failure with\n> --committer-date-is-author-date that Jonathan reported which I'll post\n> next week - the bug was not in this series, it was just exposed by\n> it. I'll try and read through this series next week as well.\n>\n> Best Wishes\n\nThanks.\n\nAs to the \"--am\" option, I do not care too deeply about it anymore,\nso if there is nothing else that we need to further polish, should\nwe move this forward to 'next' soonish?\n"},{"id":"391200","messageId":"CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com","threadId":"52496","inReplyTo":"xmqqa75w68wd.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 00/19] rebase: make the default backend configurable","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-02-05T22:38:07Z","receivedAt":"2020-02-05T22:38:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 5, 2020 at 1:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>\n> >> Changes possibly missing from this version, for discussion:\n> >>\n> >>   * I did not remove the --am option as suggested by Phillip, since Junio and\n> >>     Phillip were still discussing whether it is wanted/needed.\n> >>   * I did not address the last two items Jonathan brought up as I\n> >>     couldn't find enough information to reproduce or understand the problems.\n> >\n> > I think I've got a fix for the failure with\n> > --committer-date-is-author-date that Jonathan reported which I'll post\n> > next week - the bug was not in this series, it was just exposed by\n> > it. I'll try and read through this series next week as well.\n> >\n> > Best Wishes\n>\n> Thanks.\n>\n> As to the \"--am\" option, I do not care too deeply about it anymore,\n> so if there is nothing else that we need to further polish, should\n> we move this forward to 'next' soonish?\n\nI was hoping to hear back from Phillip, as he always provides great\ncomments.  I believe I've addressed all his comments up through v3,\nbut he hasn't had time in the last 2.5 weeks to review v4.\n\nThere was also an issue surrounding post-commit hooks that I think\nJonathan and/or Emily were looking in to.  I haven't heard from them\nin a while, but I didn't think the issue was a blocker either.  If it\nis, I can try to help push something along.\n\nSo, the current state is that there's nothing left for me to polish\nthat I know of.  If others know of things I've missed or want to\nreview v4 and point out changes I should make, I'm happy to make them.\n"},{"id":"391404","messageId":"09f2a9bd-1fd2-b44b-a279-baf37ae23391@gmail.com","threadId":"52496","inReplyTo":"3ea48d53940cb7e1a82a8a6eb819497c0448af6a.1579155273.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-02-09T15:59:06Z","receivedAt":"2020-02-09T15:59:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Commit b00bf1c9a8dd (\"git-rebase: make --allow-empty-message the\n> default\", 2018-06-27) made --allow-empty-message the default and thus\n> turned --allow-empty-message into a no-op but did not update the\n> documentation to reflect this.  Update the documentation now, and hide\n> the option from the normal -h output since it is not useful.\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>   Documentation/git-rebase.txt |  7 ++++---\n>   builtin/rebase.c             | 12 +++++++-----\n>   2 files changed, 11 insertions(+), 8 deletions(-)\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 0c4f038dd6..c83be7ffc2 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.\n>   See also INCOMPATIBLE OPTIONS below.\n>   \n>   --allow-empty-message::\n> -\tBy default, rebasing commits with an empty message will fail.\n> -\tThis option overrides that behavior, allowing commits with empty\n> -\tmessages to be rebased.\n> +\tNo-op.  Rebasing commits with an empty message used to fail\n> +\tand this option would override that behavior, allowing commits\n> +\twith empty messages to be rebased.  Now commits with an empty\n> +\tmessage do not cause rebasing to halt.\n\nDo we want to mention that the user can pass --no-allow-empty-message to \noverride this?\n\nBest Wishes\n\nPhillip\n\n>   +\n>   See also INCOMPATIBLE OPTIONS below.\n>   \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 8081741f8a..faa4e0d406 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n>   \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n>   \t\t\t   REBASE_FORCE),\n>   \t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n> -\t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n> -\t\t\t N_(\"allow commits with empty messages\")),\n> +\t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n> +\t\t\t   N_(\"allow commits with empty messages\"),\n> +\t\t\t   PARSE_OPT_HIDDEN),\n>   \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n>   \t\tOPT_BOOL(0, \"rebase-cousins\", &opts.rebase_cousins,\n>   \t\t\t N_(\"keep original branch points of cousins\")),\n> @@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\tOPT_STRING_LIST('x', \"exec\", &exec, N_(\"exec\"),\n>   \t\t\t\tN_(\"add exec lines after each commit of the \"\n>   \t\t\t\t   \"editable list\")),\n> -\t\tOPT_BOOL(0, \"allow-empty-message\",\n> -\t\t\t &options.allow_empty_message,\n> -\t\t\t N_(\"allow rebasing commits with empty messages\")),\n> +\t\tOPT_BOOL_F(0, \"allow-empty-message\",\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\t\tN_(\"try to rebase merges instead of skipping them\"),\n> \n"},{"id":"391405","messageId":"805e3edd-d479-16b8-c67f-d8ea34176389@gmail.com","threadId":"52496","inReplyTo":"179f82ab83e669ca8da7ce999607cae584bc18df.1579155273.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-02-09T15:59:40Z","receivedAt":"2020-02-09T15:59:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Different rebase backends have different treatment for commits which\n> start empty (i.e. have no changes relative to their parent), and the\n> --keep-empty option was added at some point to allow adjusting behavior\n> for the interactive backend.\n\n--keep-empty was introduced by 90e1818f9a (\"git-rebase: add keep_empty \nflag\", 2012-04-20) and was respected for am based rebases as well \n(although the implementation was broken if there were any conflicts)\n\n> The handling of commits which start empty\n> is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n> --allow-empty-message the default, 2018-06-27), which pointed out that\n> the behavior for various backends is often more happenstance than\n> design.  The specific change made in that commit is actually quite\n> relevant as well and much of the logic there directly applies here.\n> \n> It makes a lot of sense in 'git commit' to error out on the creation of\n> empty commits, unless an override flag is provided.  However, once\n> someone determines that there is a rare case that merits using the\n> manual override to create such a commit, it is somewhere between\n> annoying and harmful to have to take extra steps to keep such\n> intentional commits around.  Granted, empty commits are quite rare,\n> which is why handling of them doesn't get considered much and folks tend\n> to defer to existing (accidental) behavior and assume there was a reason\n> for it, leading them to just add flags (--keep-empty in this case) that\n> allow them to override the bad defaults.  Fix the interactive backend so\n> that --keep-empty is the default, much like we did with\n> --allow-empty-message.  The am backend should also be fixed to have\n> --keep-empty semantics for commits that start empty, but that is not\n> included in this patch other than a testcase documenting the failure.\n\nThe detailed commit message is great, however I think it would be good \nto point out that this changes things so that --no-keep-empty is parsed \nbut does nothing - it is no longer possible to get that behavior. I'm \nnot  entirely sure about making --keep-empty hidden given that the am \nbackend has a different behavior without it but as we're changing the \ndefault backend it probably does not matter and it looks like \n--keep-empty combined with an am specific option will still error out \nwhich is good.\n\nOverall I like this change and it simplifies the implementation which is \nnice. Hopefully no one was relying on --no-keep-empty\n\n> Note that there was one test in t3421 which appears to have been written\n> expecting --keep-empty to not be the default as correct behavior.  This\n> test was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\n> empty commits\", 2013-06-06), which was part of a series focusing on\n> rebase topology and which had an interesting original cover letter at\n> https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\n> which noted\n>      Your input especially appreciated on whether you agree with the\n>      intent of the test cases.\n> and then went into a long example about how one of the many tests added\n> had several questions about whether it was correct.  As such, I believe\n> most the tests in that series were about testing rebase topology with as\n> many different flags as possible and were not trying to state in general\n> how those flags should behave otherwise.\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>   Documentation/git-rebase.txt      | 24 ++++++-----\n>   builtin/rebase.c                  | 28 ++++++++----\n>   rebase-interactive.c              |  7 +--\n>   rebase-interactive.h              |  2 +-\n>   sequencer.c                       | 20 +++------\n>   sequencer.h                       |  2 +-\n>   t/t3421-rebase-topology-linear.sh | 16 +++----\n>   t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++\n>   t/t3427-rebase-subtree.sh         | 12 +++---\n>   9 files changed, 128 insertions(+), 55 deletions(-)\n>   create mode 100755 t/t3424-rebase-empty.sh\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index c83be7ffc2..1d19542d79 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.\n>   \tunchanged as a result.\n>   \n>   --keep-empty::\n> -\tKeep the commits that do not change anything from its\n> -\tparents in the result.\n> +\tNo-op.  Rebasing commits that started empty (had no change\n> +\trelative to their parent) used to fail and this option would\n> +\toverride that behavior, allowing commits with empty changes to\n> +\tbe rebased.  Now commits with no changes do not cause rebasing\n> +\tto halt.\n\nMaybe mention that --no-keep-empty is no longer respected?\n\n> -See also INCOMPATIBLE OPTIONS below.\n> +See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n>   \n>   --allow-empty-message::\n>   \tNo-op.  Rebasing commits with an empty message used to fail\n> @@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.\n>   Empty commits\n>   ~~~~~~~~~~~~~\n>   \n> -The am backend drops any \"empty\" commits, regardless of whether the\n> -commit started empty (had no changes relative to its parent to\n> -start with) or ended empty (all changes were already applied\n> -upstream in other commits).\n> +The am backend unfortunately drops intentionally empty commits, i.e.\n> +commits that started empty, though these are rare in practice.  It\n> +also drops commits that become empty and has no option for controlling\n> +this behavior.\n>   \n> -The interactive backend drops commits by default that\n> -started empty and halts if it hits a commit that ended up empty.\n> -The `--keep-empty` option exists for the interactive backend to allow\n> -it to keep commits that started empty.\n> +The interactive backend keeps intentionally empty commits.\n> +Unfortunately, it always halts whenever it runs across a commit that\n> +becomes empty, even when the rebase is not explicitly interactive.\n>   \n>   Directory rename detection\n>   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index faa4e0d406..537b3241ce 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -77,7 +77,6 @@ struct rebase_options {\n>   \tconst char *action;\n>   \tint signoff;\n>   \tint allow_rerere_autoupdate;\n> -\tint keep_empty;\n>   \tint autosquash;\n>   \tchar *gpg_sign_opt;\n>   \tint autostash;\n> @@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,\n>   \n>   \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n>   \n> -\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n>   \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n>   \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n>   \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n> @@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,\n>   \treturn ret;\n>   }\n>   \n> +static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n> +\t\t\t\tint unset)\n> +{\n> +\tstruct rebase_options *opts = opt->value;\n> +\n> +\tBUG_ON_OPT_ARG(arg);\n> +\n> +\topts->type = REBASE_INTERACTIVE;\n> +\treturn 0;\n> +}\n\nThis doesn't seem to handle --no-keep-empty (but looking through the \npatch that seems to be intentional)\n\n>   static const char * const builtin_rebase_interactive_usage[] = {\n>   \tN_(\"git rebase--interactive [<options>]\"),\n>   \tNULL\n> @@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n>   \tstruct option options[] = {\n>   \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n>   \t\t\t   REBASE_FORCE),\n> -\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n> +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> +\t\t\tparse_opt_keep_empty },\n>   \t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n>   \t\t\t   N_(\"allow commits with empty messages\"),\n>   \t\t\t   PARSE_OPT_HIDDEN),\n> @@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n>   \t\topts->allow_rerere_autoupdate ?\n>   \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n>   \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n> -\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n>   \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n>   \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n>   \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n> @@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\t\t\t \"ignoring them\"),\n>   \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>   \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n> -\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n> -\t\t\t N_(\"preserve empty commits during rebase\")),\n> +\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> +\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> +\t\t\tparse_opt_keep_empty },\n>   \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n>   \t\t\t N_(\"move commits that begin with \"\n>   \t\t\t    \"squash!/fixup! under -i\")),\n> @@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \tif (!(options.flags & REBASE_NO_QUIET))\n>   \t\targv_array_push(&options.git_am_opts, \"-q\");\n>   \n> -\tif (options.keep_empty)\n> -\t\timply_interactive(&options, \"--keep-empty\");\n> -\n>   \tif (gpg_sign) {\n>   \t\tfree(options.gpg_sign_opt);\n>   \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index aa18ae82b7..7e7b4061bf 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n>   \treturn MISSING_COMMIT_CHECK_IGNORE;\n>   }\n>   \n> -void append_todo_help(unsigned keep_empty, int command_count,\n> +void append_todo_help(int command_count,\n>   \t\t      const char *shortrevisions, const char *shortonto,\n>   \t\t      struct strbuf *buf)\n>   {\n> @@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,\n>   \t\t\t\"the rebase will be aborted.\\n\\n\");\n>   \n>   \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n> -\n> -\tif (!keep_empty) {\n> -\t\tmsg = _(\"Note that empty commits are commented out\");\n> -\t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n> -\t}\n>   }\n>   \n>   int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n> diff --git a/rebase-interactive.h b/rebase-interactive.h\n> index 44dbb06311..05354ca341 100644\n> --- a/rebase-interactive.h\n> +++ b/rebase-interactive.h\n> @@ -5,7 +5,7 @@ struct strbuf;\n>   struct repository;\n>   struct todo_list;\n>   \n> -void append_todo_help(unsigned keep_empty, int command_count,\n> +void append_todo_help(int command_count,\n>   \t\t      const char *shortrevisions, const char *shortonto,\n>   \t\t      struct strbuf *buf);\n>   int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n> diff --git a/sequencer.c b/sequencer.c\n> index b9dbf1adb0..c21fc202b1 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,\n>   \t\t       struct replay_opts *opts,\n>   \t\t       struct commit *commit)\n>   {\n> -\tint index_unchanged, empty_commit;\n> +\tint index_unchanged, originally_empty;\n>   \n>   \t/*\n>   \t * Three cases:\n> @@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,\n>   \tif (opts->keep_redundant_commits)\n>   \t\treturn 1;\n>   \n> -\tempty_commit = is_original_commit_empty(commit);\n> -\tif (empty_commit < 0)\n> -\t\treturn empty_commit;\n> -\tif (!empty_commit)\n> +\toriginally_empty = is_original_commit_empty(commit);\n> +\tif (originally_empty < 0)\n> +\t\treturn originally_empty;\n> +\tif (!originally_empty)\n>   \t\treturn 0;\n>   \telse\n>   \t\treturn 1;\n> @@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n>   \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n>   \t\t\t\t   unsigned flags)\n>   {\n> -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>   \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n>   \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n>   \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n> @@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n>   \t\tif (!to_merge) {\n>   \t\t\t/* non-merge commit: easy case */\n>   \t\t\tstrbuf_reset(&buf);\n> -\t\t\tif (!keep_empty && is_empty)\n> -\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n>   \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n>   \t\t\t\t    oid_to_hex(&commit->object.oid),\n>   \t\t\t\t    oneline.buf);\n> @@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n>   \tstruct pretty_print_context pp = {0};\n>   \tstruct rev_info revs;\n>   \tstruct commit *commit;\n> -\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n>   \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n>   \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n>   \n> @@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n>   \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n>   \n>   \twhile ((commit = get_revision(&revs))) {\n> -\t\tint is_empty  = is_original_commit_empty(commit);\n> +\t\tint is_empty = is_original_commit_empty(commit);\n>   \n>   \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n>   \t\t\tcontinue;\n> -\t\tif (!keep_empty && is_empty)\n> -\t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n>   \t\tstrbuf_addf(out, \"%s %s \", insn,\n>   \t\t\t    oid_to_hex(&commit->object.oid));\n>   \t\tpretty_print_commit(&pp, commit, out);\n> @@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n>   \n>   \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n>   \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n> -\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n> +\t\tappend_todo_help(count_commands(todo_list),\n>   \t\t\t\t shortrevisions, shortonto, &buf);\n>   \n>   \tres = write_message(buf.buf, buf.len, file, 0);\n> diff --git a/sequencer.h b/sequencer.h\n> index 9f9ae291e3..c165e0ff25 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n>   int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n>   int sequencer_remove_state(struct replay_opts *opts);\n>   \n> -#define TODO_LIST_KEEP_EMPTY (1U << 0)\n> +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n>   #define TODO_LIST_SHORTEN_IDS (1U << 1)\n>   #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n>   #define TODO_LIST_REBASE_MERGES (1U << 3)\n> diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n> index 325072b0a3..57334dca7e 100755\n> --- a/t/t3421-rebase-topology-linear.sh\n> +++ b/t/t3421-rebase-topology-linear.sh\n> @@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '\n>   test_run_rebase () {\n>   \tresult=$1\n>   \tshift\n> -\ttest_expect_$result \"rebase $* drops empty commit\" \"\n> +\ttest_expect_$result \"rebase $* keeps begin-empty commits\" \"\n>   \t\treset_rebase &&\n> -\t\tgit rebase $* c l &&\n> -\t\ttest_cmp_rev c HEAD~2 &&\n> -\t\ttest_linear_range 'd l' c..\n> +\t\tgit rebase $* j l &&\n> +\t\ttest_cmp_rev c HEAD~4 &&\n> +\t\ttest_linear_range 'j d k l' c..\n>   \t\"\n>   }\n> -test_run_rebase success ''\n> +test_run_rebase failure ''\n>   test_run_rebase success -m\n>   test_run_rebase success -i\n> -test_have_prereq !REBASE_P || test_run_rebase success -p\n> +test_have_prereq !REBASE_P || test_run_rebase failure -p\n>   \n>   test_run_rebase () {\n>   \tresult=$1\n> @@ -230,7 +230,7 @@ test_run_rebase () {\n>   test_run_rebase success ''\n>   test_run_rebase success -m\n>   test_run_rebase success -i\n> -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> +test_have_prereq !REBASE_P || test_run_rebase success -p\n>   \n>   test_run_rebase () {\n>   \tresult=$1\n> @@ -245,7 +245,7 @@ test_run_rebase () {\n>   test_run_rebase success ''\n>   test_run_rebase success -m\n>   test_run_rebase success -i\n> -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> +test_have_prereq !REBASE_P || test_run_rebase success -p\n>   test_run_rebase success --rebase-merges\n>   \n>   #       m\n> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n> new file mode 100755\n> index 0000000000..22d97e143b\n> --- /dev/null\n> +++ b/t/t3424-rebase-empty.sh\n> @@ -0,0 +1,72 @@\n> +#!/bin/sh\n> +\n> +test_description='git rebase of commits that start or become empty'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup test repository' '\n> +\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n> +\ttest_write_lines A B C D E F G H I J >letters &&\n> +\tgit add numbers letters &&\n> +\tgit commit -m A &&\n> +\n> +\tgit branch upstream &&\n> +\tgit branch localmods &&\n> +\n> +\tgit checkout upstream &&\n> +\ttest_write_lines A B C D E >letters &&\n> +\tgit add letters &&\n> +\tgit commit -m B &&\n> +\n> +\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n> +\tgit add numbers &&\n> +\tgit commit -m C &&\n> +\n> +\tgit checkout localmods &&\n> +\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n> +\tgit add numbers &&\n> +\tgit commit -m C2 &&\n> +\n> +\tgit commit --allow-empty -m D &&\n> +\n> +\ttest_write_lines A B C D E >letters &&\n> +\tgit add letters &&\n> +\tgit commit -m \"Five letters ought to be enough for anybody\"\n> +'\n> +\n> +test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n> +\ttest_when_finished \"git rebase --abort\" &&\n> +\tgit checkout -B testing localmods &&\n> +\t# rebase (--am) should not drop commits that start empty\n> +\tgit rebase upstream &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_failure 'rebase --merge with a variety of empty commits' '\n> +\ttest_when_finished \"git rebase --abort\" &&\n> +\tgit checkout -B testing localmods &&\n> +\t# rebase --merge should not halt on the commit that becomes empty\n> +\tgit rebase --merge upstream &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n\nThis doesn't do any harm but I'm not sure it's needed\n\nBest Wishes\n\nPhillip\n\n> +test_expect_success 'rebase --interactive with a variety of empty commits' '\n> +\tgit checkout -B testing localmods &&\n> +\ttest_must_fail git rebase --interactive upstream &&\n> +\n> +\tgit rebase --skip &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_done\n> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n> index bec48e6a1f..8dceef61cf 100755\n> --- a/t/t3427-rebase-subtree.sh\n> +++ b/t/t3427-rebase-subtree.sh\n> @@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n>   \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>   '\n>   \n> -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --onto commit' '\n>   \treset_rebase &&\n>   \tgit checkout -b rebase-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n>   \t: first pick results in no changes &&\n> -\tgit rebase --continue &&\n> +\tgit rebase --skip &&\n>   \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>   \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>   \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>   '\n>   \n> -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n>   \treset_rebase &&\n>   \tgit checkout -b rebase-merges-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n>   \t: first pick results in no changes &&\n> -\tgit rebase --continue &&\n> +\tgit rebase --skip &&\n>   \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n>   \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n>   \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n> \n"},{"id":"391451","messageId":"4da2874e-b96a-e81c-c2ac-4b3f06c9926d@gmail.com","threadId":"52496","inReplyTo":"c9542a2abe0da284c28f1c71566db8fc5c47c1a5.1579155273.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-02-10T14:27:23Z","receivedAt":"2020-02-10T14:27:34Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> As established in the previous commit and commit b00bf1c9a8dd\n> (git-rebase: make --allow-empty-message the default, 2018-06-27), the\n> behavior for rebase with different backends in various edge or corner\n> cases is often more happenstance than design.  This commit addresses\n> another such corner case: commits which \"become empty\".\n> \n> A careful reader may note that there are two types of commits which would\n> become empty due to a rebase:\n> \n>    * [clean cherry-pick] Commits which are clean cherry-picks of upstream\n>      commits, as determined by `git log --cherry-mark ...`.  Re-applying\n>      these commits would result in an empty set of changes and a\n>      duplicative commit message; i.e. these are commits that have\n>      \"already been applied\" upstream.\n> \n>    * [become empty] Commits which are not empty to start, are not clean\n>      cherry-picks of upstream commits, but which still become empty after\n>      being rebased.  This happens e.g. when a commit has changes which\n>      are a strict subset of the changes in an upstream commit, or when\n>      the changes of a commit can be found spread across or among several\n>      upstream commits.\n> \n> Clearly, in both cases the changes in the commit in question are found\n> upstream already, but the commit message may not be in the latter case.\n> \n> When cherry-mark can determine a commit is already upstream, then\n> because of how cherry-mark works this means the upstream commit message\n> was about the *exact* same set of changes.  Thus, the commit messages\n> can be assumed to be fully interchangeable (and are in fact likely to be\n> completely identical).  As such, the clean cherry-pick case represents a\n> case when there is no information to be gained by keeping the extra\n> commit around.  All rebase types have always dropped these commits, and\n> no one to my knowledge has ever requested that we do otherwise.\n> \n> For many of the become empty cases (and likely even most), we will also\n> be able to drop the commit without loss of information -- but this isn't\n> quite always the case.  Since these commits represent cases that were\n> not clean cherry-picks, there is no upstream commit message explaining\n> the same set of changes.  Projects with good commit message hygiene will\n> likely have the explanation from our commit message contained within or\n> spread among the relevant upstream commits, but not all projects run\n> that way.  As such, the commit message of the commit being rebased may\n> have reasoning that suggests additional changes that should be made to\n> adapt to the new base, or it may have information that someone wants to\n> add as a note to another commit, or perhaps someone even wants to create\n> an empty commit with the commit message as-is.\n> \n> Junio commented on the \"become-empty\" types of commits as follows[1]:\n> \n>      WRT a change that ends up being empty (as opposed to a change that\n>      is empty from the beginning), I'd think that the current behaviour\n>      is desireable one.  \"am\" based rebase is solely to transplant an\n>      existing history and want to stop much less than \"interactive\" one\n>      whose purpose is to polish a series before making it publishable,\n>      and asking for confirmation (\"this has become empty--do you want to\n>      drop it?\") is more appropriate from the workflow point of view.\n> \n> [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/\n> \n> I would simply add that his arguments for \"am\"-based rebases actually\n> apply to all non-explicitly-interactive rebases.  Also, since we are\n> stating that different cases should have different defaults, it may be\n> worth providing a flag to allow users to select which behavior they want\n> for these commits.\n> \n> Introduce a new command line flag for selecting the desired behavior:\n>      --empty={drop,keep,ask}\n> with the definitions:\n>      drop: drop commits which become empty\n>      keep: keep commits which become empty\n>      ask:  provide the user a chance to interact and pick what to do with\n>            commits which become empty on a case-by-case basis\n> \n> In line with Junio's suggestion, if the --empty flag is not specified,\n> pick defaults as follows:\n>      explicitly interactive: ask\n>      otherwise: drop\n\nLooking at the implementation there is a third option - if `--exec` is \ngiven without `-i` then the default is \"keep\". I'm not sure if having \ndifferent defaults is convenient or confusing but don't feel that \nstrongly about it. I've got a few minor comments below (the mains ones \nare saying which commit has been dropped and testing the default \nbehavior when --empty is not given) but basically I like the new patch. \nThanks for working on this, the commit message does a good job of \nexplaining the changes.\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>   Documentation/git-rebase.txt | 27 ++++++++++++++++---\n>   builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++\n>   sequencer.c                  | 48 +++++++++++++++++++++++++--------\n>   sequencer.h                  |  1 +\n>   t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----\n>   t/t3427-rebase-subtree.sh    |  8 +++---\n>   6 files changed, 161 insertions(+), 25 deletions(-)\n> \n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 1d19542d79..551a91d764 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.\n>   \toriginal branch. The index and working tree are also left\n>   \tunchanged as a result.\n>   \n> +--empty={drop,keep,ask}::\n> +\tHow to handle commits that are not empty to start and are not\n> +\tclean cherry-picks of any upstream commit, but which become\n> +\tempty after rebasing (because they contain a subset of already\n> +\tupstream changes).  With drop (the default), commits that\n> +\tbecome empty are dropped.  With keep, such commits are kept.\n> +\tWith ask (implied by --interactive), the rebase will halt when\n> +\tan empty commit is applied allowing you to choose whether to\n> +\tdrop it, edit files more, or just commit the empty changes.\n\nWe should probably document the default for --exec without -i\n\n> +Note that commits which start empty are kept, and commits which are\n> +clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n> +always dropped.\n> ++\n> +See also INCOMPATIBLE OPTIONS below.\n> +\n>   --keep-empty::\n>   \tNo-op.  Rebasing commits that started empty (had no change\n>   \trelative to their parent) used to fail and this option would\n> @@ -561,6 +577,7 @@ are incompatible with the following options:\n>    * --interactive\n>    * --exec\n>    * --keep-empty\n> + * --empty=\n>    * --edit-todo\n>    * --root when used in combination with --onto\n>   \n> @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:\n>    * --preserve-merges and --interactive\n>    * --preserve-merges and --signoff\n>    * --preserve-merges and --rebase-merges\n> + * --preserve-merges and --empty=\n>    * --keep-base and --onto\n>    * --keep-base and --root\n>   \n> @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It\n>   also drops commits that become empty and has no option for controlling\n>   this behavior.\n>   \n> -The interactive backend keeps intentionally empty commits.\n> -Unfortunately, it always halts whenever it runs across a commit that\n> -becomes empty, even when the rebase is not explicitly interactive.\n> +The interactive backend keeps intentionally empty commits.  Similar to\n> +the am backend, by default the interactive backend drops commits that\n> +become empty unless -i/--interactive is specified (in which case it\n> +stops and asks the user what to do).  The interactive backend also has\n> +an --empty={drop,keep,ask} option for changing the behavior of\n> +handling commits that become empty.\n>   \n>   Directory rename detection\n>   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 537b3241ce..c299869e7b 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -50,8 +50,16 @@ enum rebase_type {\n>   \tREBASE_PRESERVE_MERGES\n>   };\n>   \n> +enum empty_type {\n> +\tEMPTY_UNSPECIFIED = -1,\n> +\tEMPTY_DROP,\n> +\tEMPTY_KEEP,\n> +\tEMPTY_ASK\n> +};\n> +\n>   struct rebase_options {\n>   \tenum rebase_type type;\n> +\tenum empty_type empty;\n>   \tconst char *state_dir;\n>   \tstruct commit *upstream;\n>   \tconst char *upstream_name;\n> @@ -91,6 +99,7 @@ struct rebase_options {\n>   \n>   #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n>   \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n> +\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n>   \t\t.flags = REBASE_NO_QUIET, \t\t\\\n>   \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n>   \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n> @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>   \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n>   \treplay.allow_empty = 1;\n>   \treplay.allow_empty_message = opts->allow_empty_message;\n> +\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n> +\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n>   \treplay.verbose = opts->flags & REBASE_VERBOSE;\n>   \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n>   \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n>   \n>   \tBUG_ON_OPT_ARG(arg);\n>   \n> +\t/*\n> +\t * If we ever want to remap --keep-empty to --empty=keep, insert:\n> +\t * \topts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n> +\t */\n>   \topts->type = REBASE_INTERACTIVE;\n>   \treturn 0;\n>   }\n> @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n>   \treturn 0;\n>   }\n>   \n> +static enum empty_type parse_empty_value(const char *value)\n> +{\n> +\tif (!strcasecmp(value, \"drop\"))\n> +\t\treturn EMPTY_DROP;\n> +\telse if (!strcasecmp(value, \"keep\"))\n> +\t\treturn EMPTY_KEEP;\n> +\telse if (!strcasecmp(value, \"ask\"))\n> +\t\treturn EMPTY_ASK;\n> +\n> +\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n> +}\n> +\n> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tstruct rebase_options *options = opt->value;\n> +\tenum empty_type value = parse_empty_value(arg);\n> +\n> +\tBUG_ON_OPT_NEG(unset);\n> +\n> +\toptions->empty = value;\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> @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\t\t\t \"ignoring them\"),\n>   \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>   \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n> +\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n> +\t\t\t       N_(\"how to handle empty commits\"),\n\nMaybe we should say \"how to handle commits that become empty\" to \ndistinguish them from commits that start empty which we always keep\n\n> +\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n>   \t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>   \t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n>   \t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \tif (!(options.flags & REBASE_NO_QUIET))\n>   \t\targv_array_push(&options.git_am_opts, \"-q\");\n>   \n> +\tif (options.empty != EMPTY_UNSPECIFIED)\n> +\t\timply_interactive(&options, \"--empty\");\n> +\n>   \tif (gpg_sign) {\n>   \t\tfree(options.gpg_sign_opt);\n>   \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n> @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\tbreak;\n>   \t}\n>   \n> +\tif (options.empty == EMPTY_UNSPECIFIED) {\n> +\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n> +\t\t\toptions.empty = EMPTY_ASK;\n> +\t\telse if (exec.nr > 0)\n> +\t\t\toptions.empty = EMPTY_KEEP;\n> +\t\telse\n> +\t\t\toptions.empty = EMPTY_DROP;\n> +\t}\n>   \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n>   \t\tdie(_(\"--reschedule-failed-exec requires \"\n>   \t\t      \"--exec or --interactive\"));\n> diff --git a/sequencer.c b/sequencer.c\n> index c21fc202b1..354d0b5a38 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n>   static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n>   static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n>   static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n>   \n>   static int git_sequencer_config(const char *k, const char *v, void *cb)\n>   {\n> @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)\n>   }\n>   \n>   /*\n> - * Do we run \"git commit\" with \"--allow-empty\"?\n> + * Should empty commits be allowed?  Return status:\n> + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)\n> + *     0: Halt on empty commit\n> + *     1: Allow empty commit\n> + *     2: Drop empty commit\n>    */\n>   static int allow_empty(struct repository *r,\n>   \t\t       struct replay_opts *opts,\n> @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,\n>   \tint index_unchanged, originally_empty;\n>   \n>   \t/*\n> -\t * Three cases:\n> +\t * Four cases:\n>   \t *\n>   \t * (1) we do not allow empty at all and error out.\n>   \t *\n> -\t * (2) we allow ones that were initially empty, but\n> -\t * forbid the ones that become empty;\n> +\t * (2) we allow ones that were initially empty, and\n> +\t *     just drop the ones that become empty\n>   \t *\n> -\t * (3) we allow both.\n> +\t * (3) we allow ones that were initially empty, but\n> +\t *     halt for the ones that become empty;\n> +\t *\n> +\t * (4) we allow both.\n>   \t */\n>   \tif (!opts->allow_empty)\n>   \t\treturn 0; /* let \"git commit\" barf as necessary */\n> @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,\n>   \toriginally_empty = is_original_commit_empty(commit);\n>   \tif (originally_empty < 0)\n>   \t\treturn originally_empty;\n> -\tif (!originally_empty)\n> -\t\treturn 0;\n> -\telse\n> +\tif (originally_empty)\n>   \t\treturn 1;\n> +\telse if (opts->drop_redundant_commits)\n> +\t\treturn 2;\n> +\telse\n> +\t\treturn 0;\n>   }\n>   \n>   static struct {\n> @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,\n>   \tchar *author = NULL;\n>   \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n>   \tstruct strbuf msgbuf = STRBUF_INIT;\n> -\tint res, unborn = 0, reword = 0, allow;\n> +\tint res, unborn = 0, reword = 0, allow, drop_commit;\n>   \n>   \tif (opts->no_commit) {\n>   \t\t/*\n> @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,\n>   \t\tgoto leave;\n>   \t}\n>   \n> +\tdrop_commit = 0;\n>   \tallow = allow_empty(r, opts, commit);\n>   \tif (allow < 0) {\n>   \t\tres = allow;\n>   \t\tgoto leave;\n> -\t} else if (allow)\n> +\t} else if (allow == 1) {\n>   \t\tflags |= ALLOW_EMPTY;\n> -\tif (!opts->no_commit) {\n> +\t} else if (allow == 2) {\n> +\t\tdrop_commit = 1;\n> +\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n\nnit pick - usually messages start with a lowercase letter. Would it be \nhelpful to explicitly state which commit is being dropped as well as \nwhy? Something like\n   dropping <oid> <subject> - patch contents already upstream\n\n> +\t} // else allow == 0 and there's nothing special to do\n\nWe don't use // for comments\n\n> +\tif (!opts->no_commit && !drop_commit) {\n>   \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>   \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n>   \t\telse\n> @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)\n>   \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n>   \t\t\topts->reschedule_failed_exec = 1;\n>   \n> +\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n> +\t\t\topts->drop_redundant_commits = 1;\n> +\n> +\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n> +\t\t\topts->keep_redundant_commits = 1;\n> +\n>   \t\tread_strategy_opts(opts, &buf);\n>   \t\tstrbuf_release(&buf);\n>   \n> @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n>   \t\twrite_file(rebase_path_gpg_sign_opt(), \"-S%s\\n\", opts->gpg_sign);\n>   \tif (opts->signoff)\n>   \t\twrite_file(rebase_path_signoff(), \"--signoff\\n\");\n> +\tif (opts->drop_redundant_commits)\n> +\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n> +\tif (opts->keep_redundant_commits)\n> +\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n>   \tif (opts->reschedule_failed_exec)\n>   \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n>   \n> diff --git a/sequencer.h b/sequencer.h\n> index c165e0ff25..3b0ab9141f 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -39,6 +39,7 @@ struct replay_opts {\n>   \tint allow_rerere_auto;\n>   \tint allow_empty;\n>   \tint allow_empty_message;\n> +\tint drop_redundant_commits;\n>   \tint keep_redundant_commits;\n>   \tint verbose;\n>   \tint quiet;\n> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n> index 22d97e143b..dcb4cb4751 100755\n> --- a/t/t3424-rebase-empty.sh\n> +++ b/t/t3424-rebase-empty.sh\n> @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '\n>   \tgit commit -m \"Five letters ought to be enough for anybody\"\n>   '\n>   \n> -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n> +test_expect_failure 'rebase (am-backend)' '\n>   \ttest_when_finished \"git rebase --abort\" &&\n>   \tgit checkout -B testing localmods &&\n>   \t# rebase (--am) should not drop commits that start empty\n> @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n>   \ttest_cmp expect actual\n>   '\n>   \n> -test_expect_failure 'rebase --merge with a variety of empty commits' '\n> -\ttest_when_finished \"git rebase --abort\" &&\n> +test_expect_success 'rebase --merge --empty=drop' '\n>   \tgit checkout -B testing localmods &&\n> -\t# rebase --merge should not halt on the commit that becomes empty\n> -\tgit rebase --merge upstream &&\n> +\tgit rebase --merge --empty=drop upstream &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --merge --empty=keep' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --merge --empty=keep upstream &&\n> +\n> +\ttest_write_lines D C2 C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --merge --empty=ask' '\n> +\tgit checkout -B testing localmods &&\n> +\ttest_must_fail git rebase --merge --empty=ask upstream &&\n> +\n> +\tgit rebase --skip &&\n>   \n>   \ttest_write_lines D C B A >expect &&\n>   \tgit log --format=%s >actual &&\n> @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '\n>   \n>   GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>   \n> -test_expect_success 'rebase --interactive with a variety of empty commits' '\n> +test_expect_success 'rebase --interactive --empty=drop' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --interactive --empty=drop upstream &&\n> +\n> +\ttest_write_lines D C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --interactive --empty=keep' '\n> +\tgit checkout -B testing localmods &&\n> +\tgit rebase --interactive --empty=keep upstream &&\n> +\n> +\ttest_write_lines D C2 C B A >expect &&\n> +\tgit log --format=%s >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --interactive --empty=ask' '\n>   \tgit checkout -B testing localmods &&\n> -\ttest_must_fail git rebase --interactive upstream &&\n> +\ttest_must_fail git rebase --interactive --empty=ask upstream &&\n>   \n>   \tgit rebase --skip &&\n\nAs the default if --empty is not given is supposed to vary depending on \nthe other options given it would be good to test that I think\n\nBest Wishes\n\nPhillip\n\n> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n> index 8dceef61cf..79e43a370b 100755\n> --- a/t/t3427-rebase-subtree.sh\n> +++ b/t/t3427-rebase-subtree.sh\n> @@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n>   \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>   '\n>   \n> -test_expect_success 'Rebase -Xsubtree --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n>   \treset_rebase &&\n>   \tgit checkout -b rebase-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n>   \t: first pick results in no changes &&\n>   \tgit rebase --skip &&\n>   \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n> @@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '\n>   \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n>   '\n>   \n> -test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n> +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n>   \treset_rebase &&\n>   \tgit checkout -b rebase-merges-onto to-rebase &&\n> -\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n> +\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n>   \t: first pick results in no changes &&\n>   \tgit rebase --skip &&\n>   \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n> \n"},{"id":"391683","messageId":"CABPp-BHgGcp8FzrOGfbp4-5Dzda8v76GRR-8RimWfdMSZOntuQ@mail.gmail.com","threadId":"52496","inReplyTo":"09f2a9bd-1fd2-b44b-a279-baf37ae23391@gmail.com","subject":"Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-02-13T18:35:38Z","receivedAt":"2020-02-13T18:35:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Phillip,\n\nOn Sun, Feb 9, 2020 at 7:59 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n...\n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index 0c4f038dd6..c83be7ffc2 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.\n> >   See also INCOMPATIBLE OPTIONS below.\n> >\n> >   --allow-empty-message::\n> > -     By default, rebasing commits with an empty message will fail.\n> > -     This option overrides that behavior, allowing commits with empty\n> > -     messages to be rebased.\n> > +     No-op.  Rebasing commits with an empty message used to fail\n> > +     and this option would override that behavior, allowing commits\n> > +     with empty messages to be rebased.  Now commits with an empty\n> > +     message do not cause rebasing to halt.\n>\n> Do we want to mention that the user can pass --no-allow-empty-message to\n> override this?\n\nInteresting point.  I could be persuaded towards that, but I'm leaning\nagainst it based on the logic in b00bf1c9a8dd which this commit was\nbased upon.\n\nHowever, even if we do want to mention --no-allow-empty-message, I'd\nrather do that in a different series.  This patch is already slightly\ntangential to the series, but it at least fixes the documentation for\na previously-documented option -- an option that happens to be similar\nin spirit to the ones being touched by the series.  Adding\ndocumentation for a previously undocumented option, while possibly\nlaudable, feels like taking a tangent to a tangent to me.  (And this\nseries is already long enough and is still hanging after two months,\nso...)\n\n\nElijah\n"},{"id":"391688","messageId":"CABPp-BF+mOZvKymaf=sxPdFWw1S6nFxpPnTx_f25g7fQDjxoKw@mail.gmail.com","threadId":"52496","inReplyTo":"805e3edd-d479-16b8-c67f-d8ea34176389@gmail.com","subject":"Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-02-13T18:52:37Z","receivedAt":"2020-02-13T18:52:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Phillip,\n\nOn Sun, Feb 9, 2020 at 7:59 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Different rebase backends have different treatment for commits which\n> > start empty (i.e. have no changes relative to their parent), and the\n> > --keep-empty option was added at some point to allow adjusting behavior\n> > for the interactive backend.\n>\n> --keep-empty was introduced by 90e1818f9a (\"git-rebase: add keep_empty\n> flag\", 2012-04-20) and was respected for am based rebases as well\n> (although the implementation was broken if there were any conflicts)\n\nInteresting.  If you look closely at commit 90e1818f9a, however,\nyou'll note that they mentioned that format-patch and am were not\ncapable of handling the --keep-empty behavior, and so they instead\nbypassed those commands and re-implemented a non-error-handling\nversion of the sequencer backend (simply cherry-picking all the\ncommits).  Further, that re-implementation ignored other options that\nwere passed and meant to be interpreted by format-patch or am due to\nthe fact that format-patch and am were bypassed.  Thus, by a strict\nreading, it looks like --keep-empty cannot be considered to have ever\nbeen respected by the am backend.\n\nThis was made more concrete by commit  002ee2fe6825 (builtin rebase:\nsupport `keep-empty` option, 2018-09-04) when the --keep-empty flag\nwas explicitly coded to imply the sequencer backend, thus making it\nclear that this option was incompatible with am-based rebases.\n\nThis was cemented further by commit 5dacd4abdd3c (git-rebase.txt:\ndocument incompatible options, 2018-06-25), which made it clear to\nusers that the flags were incompatible.\n\nSo, yeah, my commit message isn't quite accurate, but it's not quite\nright to claim that --keep-empty worked with the am backend either;\nthe reality is a bit messier to explain.\n\n> > The handling of commits which start empty\n> > is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n> > --allow-empty-message the default, 2018-06-27), which pointed out that\n> > the behavior for various backends is often more happenstance than\n> > design.  The specific change made in that commit is actually quite\n> > relevant as well and much of the logic there directly applies here.\n> >\n> > It makes a lot of sense in 'git commit' to error out on the creation of\n> > empty commits, unless an override flag is provided.  However, once\n> > someone determines that there is a rare case that merits using the\n> > manual override to create such a commit, it is somewhere between\n> > annoying and harmful to have to take extra steps to keep such\n> > intentional commits around.  Granted, empty commits are quite rare,\n> > which is why handling of them doesn't get considered much and folks tend\n> > to defer to existing (accidental) behavior and assume there was a reason\n> > for it, leading them to just add flags (--keep-empty in this case) that\n> > allow them to override the bad defaults.  Fix the interactive backend so\n> > that --keep-empty is the default, much like we did with\n> > --allow-empty-message.  The am backend should also be fixed to have\n> > --keep-empty semantics for commits that start empty, but that is not\n> > included in this patch other than a testcase documenting the failure.\n>\n> The detailed commit message is great, however I think it would be good\n> to point out that this changes things so that --no-keep-empty is parsed\n> but does nothing - it is no longer possible to get that behavior. I'm\n> not  entirely sure about making --keep-empty hidden given that the am\n> backend has a different behavior without it but as we're changing the\n> default backend it probably does not matter and it looks like\n> --keep-empty combined with an am specific option will still error out\n> which is good.\n\nAs noted above, the --keep-empty option has always been orthogonal to\nthe am backend, even if commit messages early on might have implied\notherwise.  Since it was only relevant for sequencer backend(s) and\nit's no longer relevant for the (now only) sequencer backend, I think\nhiding the option is a good idea.\n\n> Overall I like this change and it simplifies the implementation which is\n> nice. Hopefully no one was relying on --no-keep-empty\n\n:-)\n\n> > Note that there was one test in t3421 which appears to have been written\n> > expecting --keep-empty to not be the default as correct behavior.  This\n> > test was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\n> > empty commits\", 2013-06-06), which was part of a series focusing on\n> > rebase topology and which had an interesting original cover letter at\n> > https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\n> > which noted\n> >      Your input especially appreciated on whether you agree with the\n> >      intent of the test cases.\n> > and then went into a long example about how one of the many tests added\n> > had several questions about whether it was correct.  As such, I believe\n> > most the tests in that series were about testing rebase topology with as\n> > many different flags as possible and were not trying to state in general\n> > how those flags should behave otherwise.\n> >\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >   Documentation/git-rebase.txt      | 24 ++++++-----\n> >   builtin/rebase.c                  | 28 ++++++++----\n> >   rebase-interactive.c              |  7 +--\n> >   rebase-interactive.h              |  2 +-\n> >   sequencer.c                       | 20 +++------\n> >   sequencer.h                       |  2 +-\n> >   t/t3421-rebase-topology-linear.sh | 16 +++----\n> >   t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++\n> >   t/t3427-rebase-subtree.sh         | 12 +++---\n> >   9 files changed, 128 insertions(+), 55 deletions(-)\n> >   create mode 100755 t/t3424-rebase-empty.sh\n> >\n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index c83be7ffc2..1d19542d79 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.\n> >       unchanged as a result.\n> >\n> >   --keep-empty::\n> > -     Keep the commits that do not change anything from its\n> > -     parents in the result.\n> > +     No-op.  Rebasing commits that started empty (had no change\n> > +     relative to their parent) used to fail and this option would\n> > +     override that behavior, allowing commits with empty changes to\n> > +     be rebased.  Now commits with no changes do not cause rebasing\n> > +     to halt.\n>\n> Maybe mention that --no-keep-empty is no longer respected?\n\nPossibly, but I'm somewhat of the mind that If this option wasn't\nworth documenting before (as evidenced by the fact that it wasn't) and\nwasn't all that interesting in the first place (the only thing it\ncould do was override a previous --keep-empty), why add the\ndocumentation now --especially since it still only overrides a\nprevious --keep-empty and overriding a no-op just amounts to a\ndifferent-flavored no-op?\n\n> > -See also INCOMPATIBLE OPTIONS below.\n> > +See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n> >\n> >   --allow-empty-message::\n> >       No-op.  Rebasing commits with an empty message used to fail\n> > @@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.\n> >   Empty commits\n> >   ~~~~~~~~~~~~~\n> >\n> > -The am backend drops any \"empty\" commits, regardless of whether the\n> > -commit started empty (had no changes relative to its parent to\n> > -start with) or ended empty (all changes were already applied\n> > -upstream in other commits).\n> > +The am backend unfortunately drops intentionally empty commits, i.e.\n> > +commits that started empty, though these are rare in practice.  It\n> > +also drops commits that become empty and has no option for controlling\n> > +this behavior.\n> >\n> > -The interactive backend drops commits by default that\n> > -started empty and halts if it hits a commit that ended up empty.\n> > -The `--keep-empty` option exists for the interactive backend to allow\n> > -it to keep commits that started empty.\n> > +The interactive backend keeps intentionally empty commits.\n> > +Unfortunately, it always halts whenever it runs across a commit that\n> > +becomes empty, even when the rebase is not explicitly interactive.\n> >\n> >   Directory rename detection\n> >   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index faa4e0d406..537b3241ce 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -77,7 +77,6 @@ struct rebase_options {\n> >       const char *action;\n> >       int signoff;\n> >       int allow_rerere_autoupdate;\n> > -     int keep_empty;\n> >       int autosquash;\n> >       char *gpg_sign_opt;\n> >       int autostash;\n> > @@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,\n> >\n> >       git_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n> >\n> > -     flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n> >       flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n> >       flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n> >       flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n> > @@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,\n> >       return ret;\n> >   }\n> >\n> > +static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n> > +                             int unset)\n> > +{\n> > +     struct rebase_options *opts = opt->value;\n> > +\n> > +     BUG_ON_OPT_ARG(arg);\n> > +\n> > +     opts->type = REBASE_INTERACTIVE;\n> > +     return 0;\n> > +}\n>\n> This doesn't seem to handle --no-keep-empty (but looking through the\n> patch that seems to be intentional)\n>\n> >   static const char * const builtin_rebase_interactive_usage[] = {\n> >       N_(\"git rebase--interactive [<options>]\"),\n> >       NULL\n> > @@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n> >       struct option options[] = {\n> >               OPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n> >                          REBASE_FORCE),\n> > -             OPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n> > +             { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> > +                     N_(\"(DEPRECATED) keep empty commits\"),\n> > +                     PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> > +                     parse_opt_keep_empty },\n> >               OPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n> >                          N_(\"allow commits with empty messages\"),\n> >                          PARSE_OPT_HIDDEN),\n> > @@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n> >               opts->allow_rerere_autoupdate ?\n> >                       opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n> >                       \"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n> > -     add_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n> >       add_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n> >       add_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n> >       add_var(&script_snippet, \"cmd\", opts->cmd);\n> > @@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >                                \"ignoring them\"),\n> >                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n> >               OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n> > -             OPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n> > -                      N_(\"preserve empty commits during rebase\")),\n> > +             { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> > +                     N_(\"(DEPRECATED) keep empty commits\"),\n> > +                     PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> > +                     parse_opt_keep_empty },\n> >               OPT_BOOL(0, \"autosquash\", &options.autosquash,\n> >                        N_(\"move commits that begin with \"\n> >                           \"squash!/fixup! under -i\")),\n> > @@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >       if (!(options.flags & REBASE_NO_QUIET))\n> >               argv_array_push(&options.git_am_opts, \"-q\");\n> >\n> > -     if (options.keep_empty)\n> > -             imply_interactive(&options, \"--keep-empty\");\n> > -\n> >       if (gpg_sign) {\n> >               free(options.gpg_sign_opt);\n> >               options.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n> > diff --git a/rebase-interactive.c b/rebase-interactive.c\n> > index aa18ae82b7..7e7b4061bf 100644\n> > --- a/rebase-interactive.c\n> > +++ b/rebase-interactive.c\n> > @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n> >       return MISSING_COMMIT_CHECK_IGNORE;\n> >   }\n> >\n> > -void append_todo_help(unsigned keep_empty, int command_count,\n> > +void append_todo_help(int command_count,\n> >                     const char *shortrevisions, const char *shortonto,\n> >                     struct strbuf *buf)\n> >   {\n> > @@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,\n> >                       \"the rebase will be aborted.\\n\\n\");\n> >\n> >       strbuf_add_commented_lines(buf, msg, strlen(msg));\n> > -\n> > -     if (!keep_empty) {\n> > -             msg = _(\"Note that empty commits are commented out\");\n> > -             strbuf_add_commented_lines(buf, msg, strlen(msg));\n> > -     }\n> >   }\n> >\n> >   int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n> > diff --git a/rebase-interactive.h b/rebase-interactive.h\n> > index 44dbb06311..05354ca341 100644\n> > --- a/rebase-interactive.h\n> > +++ b/rebase-interactive.h\n> > @@ -5,7 +5,7 @@ struct strbuf;\n> >   struct repository;\n> >   struct todo_list;\n> >\n> > -void append_todo_help(unsigned keep_empty, int command_count,\n> > +void append_todo_help(int command_count,\n> >                     const char *shortrevisions, const char *shortonto,\n> >                     struct strbuf *buf);\n> >   int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n> > diff --git a/sequencer.c b/sequencer.c\n> > index b9dbf1adb0..c21fc202b1 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,\n> >                      struct replay_opts *opts,\n> >                      struct commit *commit)\n> >   {\n> > -     int index_unchanged, empty_commit;\n> > +     int index_unchanged, originally_empty;\n> >\n> >       /*\n> >        * Three cases:\n> > @@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,\n> >       if (opts->keep_redundant_commits)\n> >               return 1;\n> >\n> > -     empty_commit = is_original_commit_empty(commit);\n> > -     if (empty_commit < 0)\n> > -             return empty_commit;\n> > -     if (!empty_commit)\n> > +     originally_empty = is_original_commit_empty(commit);\n> > +     if (originally_empty < 0)\n> > +             return originally_empty;\n> > +     if (!originally_empty)\n> >               return 0;\n> >       else\n> >               return 1;\n> > @@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n> >                                  struct rev_info *revs, struct strbuf *out,\n> >                                  unsigned flags)\n> >   {\n> > -     int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> >       int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n> >       int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n> >       struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n> > @@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n> >               if (!to_merge) {\n> >                       /* non-merge commit: easy case */\n> >                       strbuf_reset(&buf);\n> > -                     if (!keep_empty && is_empty)\n> > -                             strbuf_addf(&buf, \"%c \", comment_line_char);\n> >                       strbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n> >                                   oid_to_hex(&commit->object.oid),\n> >                                   oneline.buf);\n> > @@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n> >       struct pretty_print_context pp = {0};\n> >       struct rev_info revs;\n> >       struct commit *commit;\n> > -     int keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n> >       const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n> >       int rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n> >\n> > @@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n> >               return make_script_with_merges(&pp, &revs, out, flags);\n> >\n> >       while ((commit = get_revision(&revs))) {\n> > -             int is_empty  = is_original_commit_empty(commit);\n> > +             int is_empty = is_original_commit_empty(commit);\n> >\n> >               if (!is_empty && (commit->object.flags & PATCHSAME))\n> >                       continue;\n> > -             if (!keep_empty && is_empty)\n> > -                     strbuf_addf(out, \"%c \", comment_line_char);\n> >               strbuf_addf(out, \"%s %s \", insn,\n> >                           oid_to_hex(&commit->object.oid));\n> >               pretty_print_commit(&pp, commit, out);\n> > @@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n> >\n> >       todo_list_to_strbuf(r, todo_list, &buf, num, flags);\n> >       if (flags & TODO_LIST_APPEND_TODO_HELP)\n> > -             append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n> > +             append_todo_help(count_commands(todo_list),\n> >                                shortrevisions, shortonto, &buf);\n> >\n> >       res = write_message(buf.buf, buf.len, file, 0);\n> > diff --git a/sequencer.h b/sequencer.h\n> > index 9f9ae291e3..c165e0ff25 100644\n> > --- a/sequencer.h\n> > +++ b/sequencer.h\n> > @@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n> >   int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n> >   int sequencer_remove_state(struct replay_opts *opts);\n> >\n> > -#define TODO_LIST_KEEP_EMPTY (1U << 0)\n> > +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n> >   #define TODO_LIST_SHORTEN_IDS (1U << 1)\n> >   #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n> >   #define TODO_LIST_REBASE_MERGES (1U << 3)\n> > diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n> > index 325072b0a3..57334dca7e 100755\n> > --- a/t/t3421-rebase-topology-linear.sh\n> > +++ b/t/t3421-rebase-topology-linear.sh\n> > @@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '\n> >   test_run_rebase () {\n> >       result=$1\n> >       shift\n> > -     test_expect_$result \"rebase $* drops empty commit\" \"\n> > +     test_expect_$result \"rebase $* keeps begin-empty commits\" \"\n> >               reset_rebase &&\n> > -             git rebase $* c l &&\n> > -             test_cmp_rev c HEAD~2 &&\n> > -             test_linear_range 'd l' c..\n> > +             git rebase $* j l &&\n> > +             test_cmp_rev c HEAD~4 &&\n> > +             test_linear_range 'j d k l' c..\n> >       \"\n> >   }\n> > -test_run_rebase success ''\n> > +test_run_rebase failure ''\n> >   test_run_rebase success -m\n> >   test_run_rebase success -i\n> > -test_have_prereq !REBASE_P || test_run_rebase success -p\n> > +test_have_prereq !REBASE_P || test_run_rebase failure -p\n> >\n> >   test_run_rebase () {\n> >       result=$1\n> > @@ -230,7 +230,7 @@ test_run_rebase () {\n> >   test_run_rebase success ''\n> >   test_run_rebase success -m\n> >   test_run_rebase success -i\n> > -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> > +test_have_prereq !REBASE_P || test_run_rebase success -p\n> >\n> >   test_run_rebase () {\n> >       result=$1\n> > @@ -245,7 +245,7 @@ test_run_rebase () {\n> >   test_run_rebase success ''\n> >   test_run_rebase success -m\n> >   test_run_rebase success -i\n> > -test_have_prereq !REBASE_P || test_run_rebase failure -p\n> > +test_have_prereq !REBASE_P || test_run_rebase success -p\n> >   test_run_rebase success --rebase-merges\n> >\n> >   #       m\n> > diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n> > new file mode 100755\n> > index 0000000000..22d97e143b\n> > --- /dev/null\n> > +++ b/t/t3424-rebase-empty.sh\n> > @@ -0,0 +1,72 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git rebase of commits that start or become empty'\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +test_expect_success 'setup test repository' '\n> > +     test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n> > +     test_write_lines A B C D E F G H I J >letters &&\n> > +     git add numbers letters &&\n> > +     git commit -m A &&\n> > +\n> > +     git branch upstream &&\n> > +     git branch localmods &&\n> > +\n> > +     git checkout upstream &&\n> > +     test_write_lines A B C D E >letters &&\n> > +     git add letters &&\n> > +     git commit -m B &&\n> > +\n> > +     test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n> > +     git add numbers &&\n> > +     git commit -m C &&\n> > +\n> > +     git checkout localmods &&\n> > +     test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n> > +     git add numbers &&\n> > +     git commit -m C2 &&\n> > +\n> > +     git commit --allow-empty -m D &&\n> > +\n> > +     test_write_lines A B C D E >letters &&\n> > +     git add letters &&\n> > +     git commit -m \"Five letters ought to be enough for anybody\"\n> > +'\n> > +\n> > +test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n> > +     test_when_finished \"git rebase --abort\" &&\n> > +     git checkout -B testing localmods &&\n> > +     # rebase (--am) should not drop commits that start empty\n> > +     git rebase upstream &&\n> > +\n> > +     test_write_lines D C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +test_expect_failure 'rebase --merge with a variety of empty commits' '\n> > +     test_when_finished \"git rebase --abort\" &&\n> > +     git checkout -B testing localmods &&\n> > +     # rebase --merge should not halt on the commit that becomes empty\n> > +     git rebase --merge upstream &&\n> > +\n> > +     test_write_lines D C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>\n> This doesn't do any harm but I'm not sure it's needed\n\nIndeed, good catch.  I'll remove it.\n\n\nThanks for digging in and providing feedback!\n\n\nElijah\n"},{"id":"391689","messageId":"CABPp-BGRGkjBk9aY8DUXJY52e5u_aAVd8-dspEcW6Se3v-G4-Q@mail.gmail.com","threadId":"52496","inReplyTo":"4da2874e-b96a-e81c-c2ac-4b3f06c9926d@gmail.com","subject":"Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-02-13T18:54:02Z","receivedAt":"2020-02-13T18:54:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 10, 2020 at 6:27 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > As established in the previous commit and commit b00bf1c9a8dd\n> > (git-rebase: make --allow-empty-message the default, 2018-06-27), the\n> > behavior for rebase with different backends in various edge or corner\n> > cases is often more happenstance than design.  This commit addresses\n> > another such corner case: commits which \"become empty\".\n> >\n> > A careful reader may note that there are two types of commits which would\n> > become empty due to a rebase:\n> >\n> >    * [clean cherry-pick] Commits which are clean cherry-picks of upstream\n> >      commits, as determined by `git log --cherry-mark ...`.  Re-applying\n> >      these commits would result in an empty set of changes and a\n> >      duplicative commit message; i.e. these are commits that have\n> >      \"already been applied\" upstream.\n> >\n> >    * [become empty] Commits which are not empty to start, are not clean\n> >      cherry-picks of upstream commits, but which still become empty after\n> >      being rebased.  This happens e.g. when a commit has changes which\n> >      are a strict subset of the changes in an upstream commit, or when\n> >      the changes of a commit can be found spread across or among several\n> >      upstream commits.\n> >\n> > Clearly, in both cases the changes in the commit in question are found\n> > upstream already, but the commit message may not be in the latter case.\n> >\n> > When cherry-mark can determine a commit is already upstream, then\n> > because of how cherry-mark works this means the upstream commit message\n> > was about the *exact* same set of changes.  Thus, the commit messages\n> > can be assumed to be fully interchangeable (and are in fact likely to be\n> > completely identical).  As such, the clean cherry-pick case represents a\n> > case when there is no information to be gained by keeping the extra\n> > commit around.  All rebase types have always dropped these commits, and\n> > no one to my knowledge has ever requested that we do otherwise.\n> >\n> > For many of the become empty cases (and likely even most), we will also\n> > be able to drop the commit without loss of information -- but this isn't\n> > quite always the case.  Since these commits represent cases that were\n> > not clean cherry-picks, there is no upstream commit message explaining\n> > the same set of changes.  Projects with good commit message hygiene will\n> > likely have the explanation from our commit message contained within or\n> > spread among the relevant upstream commits, but not all projects run\n> > that way.  As such, the commit message of the commit being rebased may\n> > have reasoning that suggests additional changes that should be made to\n> > adapt to the new base, or it may have information that someone wants to\n> > add as a note to another commit, or perhaps someone even wants to create\n> > an empty commit with the commit message as-is.\n> >\n> > Junio commented on the \"become-empty\" types of commits as follows[1]:\n> >\n> >      WRT a change that ends up being empty (as opposed to a change that\n> >      is empty from the beginning), I'd think that the current behaviour\n> >      is desireable one.  \"am\" based rebase is solely to transplant an\n> >      existing history and want to stop much less than \"interactive\" one\n> >      whose purpose is to polish a series before making it publishable,\n> >      and asking for confirmation (\"this has become empty--do you want to\n> >      drop it?\") is more appropriate from the workflow point of view.\n> >\n> > [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/\n> >\n> > I would simply add that his arguments for \"am\"-based rebases actually\n> > apply to all non-explicitly-interactive rebases.  Also, since we are\n> > stating that different cases should have different defaults, it may be\n> > worth providing a flag to allow users to select which behavior they want\n> > for these commits.\n> >\n> > Introduce a new command line flag for selecting the desired behavior:\n> >      --empty={drop,keep,ask}\n> > with the definitions:\n> >      drop: drop commits which become empty\n> >      keep: keep commits which become empty\n> >      ask:  provide the user a chance to interact and pick what to do with\n> >            commits which become empty on a case-by-case basis\n> >\n> > In line with Junio's suggestion, if the --empty flag is not specified,\n> > pick defaults as follows:\n> >      explicitly interactive: ask\n> >      otherwise: drop\n>\n> Looking at the implementation there is a third option - if `--exec` is\n> given without `-i` then the default is \"keep\". I'm not sure if having\n> different defaults is convenient or confusing but don't feel that\n> strongly about it.\n\nHeh, in https://lore.kernel.org/git/404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com/\nyou argued that having different defaults was confusing and sounded\nlike you felt strongly about it.  Granted, there has been a lot of\nsimplification to the implementation (and description) since then but\nI'm still inclined to go with the simpler and more easily explained\nbehavior for the defaults based on what you said there.\n\n> I've got a few minor comments below (the mains ones\n> are saying which commit has been dropped and testing the default\n> behavior when --empty is not given) but basically I like the new patch.\n> Thanks for working on this, the commit message does a good job of\n> explaining the changes.\n\n:-)\n\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >   Documentation/git-rebase.txt | 27 ++++++++++++++++---\n> >   builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++\n> >   sequencer.c                  | 48 +++++++++++++++++++++++++--------\n> >   sequencer.h                  |  1 +\n> >   t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----\n> >   t/t3427-rebase-subtree.sh    |  8 +++---\n> >   6 files changed, 161 insertions(+), 25 deletions(-)\n> >\n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index 1d19542d79..551a91d764 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.\n> >       original branch. The index and working tree are also left\n> >       unchanged as a result.\n> >\n> > +--empty={drop,keep,ask}::\n> > +     How to handle commits that are not empty to start and are not\n> > +     clean cherry-picks of any upstream commit, but which become\n> > +     empty after rebasing (because they contain a subset of already\n> > +     upstream changes).  With drop (the default), commits that\n> > +     become empty are dropped.  With keep, such commits are kept.\n> > +     With ask (implied by --interactive), the rebase will halt when\n> > +     an empty commit is applied allowing you to choose whether to\n> > +     drop it, edit files more, or just commit the empty changes.\n>\n> We should probably document the default for --exec without -i\n\nI did, but I guess it wasn't clear enough.  Maybe I should add \"Note\nthat other options like --exec will use the default of drop unless\n-i/--interactive is specified.\"?\n\n> > +Note that commits which start empty are kept, and commits which are\n> > +clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n> > +always dropped.\n> > ++\n> > +See also INCOMPATIBLE OPTIONS below.\n> > +\n> >   --keep-empty::\n> >       No-op.  Rebasing commits that started empty (had no change\n> >       relative to their parent) used to fail and this option would\n> > @@ -561,6 +577,7 @@ are incompatible with the following options:\n> >    * --interactive\n> >    * --exec\n> >    * --keep-empty\n> > + * --empty=\n> >    * --edit-todo\n> >    * --root when used in combination with --onto\n> >\n> > @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:\n> >    * --preserve-merges and --interactive\n> >    * --preserve-merges and --signoff\n> >    * --preserve-merges and --rebase-merges\n> > + * --preserve-merges and --empty=\n> >    * --keep-base and --onto\n> >    * --keep-base and --root\n> >\n> > @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It\n> >   also drops commits that become empty and has no option for controlling\n> >   this behavior.\n> >\n> > -The interactive backend keeps intentionally empty commits.\n> > -Unfortunately, it always halts whenever it runs across a commit that\n> > -becomes empty, even when the rebase is not explicitly interactive.\n> > +The interactive backend keeps intentionally empty commits.  Similar to\n> > +the am backend, by default the interactive backend drops commits that\n> > +become empty unless -i/--interactive is specified (in which case it\n> > +stops and asks the user what to do).  The interactive backend also has\n> > +an --empty={drop,keep,ask} option for changing the behavior of\n> > +handling commits that become empty.\n> >\n> >   Directory rename detection\n> >   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index 537b3241ce..c299869e7b 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -50,8 +50,16 @@ enum rebase_type {\n> >       REBASE_PRESERVE_MERGES\n> >   };\n> >\n> > +enum empty_type {\n> > +     EMPTY_UNSPECIFIED = -1,\n> > +     EMPTY_DROP,\n> > +     EMPTY_KEEP,\n> > +     EMPTY_ASK\n> > +};\n> > +\n> >   struct rebase_options {\n> >       enum rebase_type type;\n> > +     enum empty_type empty;\n> >       const char *state_dir;\n> >       struct commit *upstream;\n> >       const char *upstream_name;\n> > @@ -91,6 +99,7 @@ struct rebase_options {\n> >\n> >   #define REBASE_OPTIONS_INIT {                               \\\n> >               .type = REBASE_UNSPECIFIED,             \\\n> > +             .empty = EMPTY_UNSPECIFIED,             \\\n> >               .flags = REBASE_NO_QUIET,               \\\n> >               .git_am_opts = ARGV_ARRAY_INIT,         \\\n> >               .git_format_patch_opt = STRBUF_INIT     \\\n> > @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n> >               replay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n> >       replay.allow_empty = 1;\n> >       replay.allow_empty_message = opts->allow_empty_message;\n> > +     replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n> > +     replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n> >       replay.verbose = opts->flags & REBASE_VERBOSE;\n> >       replay.reschedule_failed_exec = opts->reschedule_failed_exec;\n> >       replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n> > @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n> >\n> >       BUG_ON_OPT_ARG(arg);\n> >\n> > +     /*\n> > +      * If we ever want to remap --keep-empty to --empty=keep, insert:\n> > +      *      opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n> > +      */\n> >       opts->type = REBASE_INTERACTIVE;\n> >       return 0;\n> >   }\n> > @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n> >       return 0;\n> >   }\n> >\n> > +static enum empty_type parse_empty_value(const char *value)\n> > +{\n> > +     if (!strcasecmp(value, \"drop\"))\n> > +             return EMPTY_DROP;\n> > +     else if (!strcasecmp(value, \"keep\"))\n> > +             return EMPTY_KEEP;\n> > +     else if (!strcasecmp(value, \"ask\"))\n> > +             return EMPTY_ASK;\n> > +\n> > +     die(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n> > +}\n> > +\n> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     struct rebase_options *options = opt->value;\n> > +     enum empty_type value = parse_empty_value(arg);\n> > +\n> > +     BUG_ON_OPT_NEG(unset);\n> > +\n> > +     options->empty = value;\n> > +     return 0;\n> > +}\n> > +\n> >   static void NORETURN error_on_missing_default_upstream(void)\n> >   {\n> >       struct branch *current_branch = branch_get(NULL);\n> > @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >                                \"ignoring them\"),\n> >                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n> >               OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n> > +             OPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n> > +                            N_(\"how to handle empty commits\"),\n>\n> Maybe we should say \"how to handle commits that become empty\" to\n> distinguish them from commits that start empty which we always keep\n\nOoh, good catch; will fix.\n\n> > +                            PARSE_OPT_NONEG, parse_opt_empty),\n> >               { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n> >                       N_(\"(DEPRECATED) keep empty commits\"),\n> >                       PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n> > @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >       if (!(options.flags & REBASE_NO_QUIET))\n> >               argv_array_push(&options.git_am_opts, \"-q\");\n> >\n> > +     if (options.empty != EMPTY_UNSPECIFIED)\n> > +             imply_interactive(&options, \"--empty\");\n> > +\n> >       if (gpg_sign) {\n> >               free(options.gpg_sign_opt);\n> >               options.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n> > @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >               break;\n> >       }\n> >\n> > +     if (options.empty == EMPTY_UNSPECIFIED) {\n> > +             if (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n> > +                     options.empty = EMPTY_ASK;\n> > +             else if (exec.nr > 0)\n> > +                     options.empty = EMPTY_KEEP;\n> > +             else\n> > +                     options.empty = EMPTY_DROP;\n> > +     }\n> >       if (reschedule_failed_exec > 0 && !is_interactive(&options))\n> >               die(_(\"--reschedule-failed-exec requires \"\n> >                     \"--exec or --interactive\"));\n> > diff --git a/sequencer.c b/sequencer.c\n> > index c21fc202b1..354d0b5a38 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n> >   static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n> >   static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n> >   static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n> > +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n> > +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n> >\n> >   static int git_sequencer_config(const char *k, const char *v, void *cb)\n> >   {\n> > @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)\n> >   }\n> >\n> >   /*\n> > - * Do we run \"git commit\" with \"--allow-empty\"?\n> > + * Should empty commits be allowed?  Return status:\n> > + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)\n> > + *     0: Halt on empty commit\n> > + *     1: Allow empty commit\n> > + *     2: Drop empty commit\n> >    */\n> >   static int allow_empty(struct repository *r,\n> >                      struct replay_opts *opts,\n> > @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,\n> >       int index_unchanged, originally_empty;\n> >\n> >       /*\n> > -      * Three cases:\n> > +      * Four cases:\n> >        *\n> >        * (1) we do not allow empty at all and error out.\n> >        *\n> > -      * (2) we allow ones that were initially empty, but\n> > -      * forbid the ones that become empty;\n> > +      * (2) we allow ones that were initially empty, and\n> > +      *     just drop the ones that become empty\n> >        *\n> > -      * (3) we allow both.\n> > +      * (3) we allow ones that were initially empty, but\n> > +      *     halt for the ones that become empty;\n> > +      *\n> > +      * (4) we allow both.\n> >        */\n> >       if (!opts->allow_empty)\n> >               return 0; /* let \"git commit\" barf as necessary */\n> > @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,\n> >       originally_empty = is_original_commit_empty(commit);\n> >       if (originally_empty < 0)\n> >               return originally_empty;\n> > -     if (!originally_empty)\n> > -             return 0;\n> > -     else\n> > +     if (originally_empty)\n> >               return 1;\n> > +     else if (opts->drop_redundant_commits)\n> > +             return 2;\n> > +     else\n> > +             return 0;\n> >   }\n> >\n> >   static struct {\n> > @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,\n> >       char *author = NULL;\n> >       struct commit_message msg = { NULL, NULL, NULL, NULL };\n> >       struct strbuf msgbuf = STRBUF_INIT;\n> > -     int res, unborn = 0, reword = 0, allow;\n> > +     int res, unborn = 0, reword = 0, allow, drop_commit;\n> >\n> >       if (opts->no_commit) {\n> >               /*\n> > @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,\n> >               goto leave;\n> >       }\n> >\n> > +     drop_commit = 0;\n> >       allow = allow_empty(r, opts, commit);\n> >       if (allow < 0) {\n> >               res = allow;\n> >               goto leave;\n> > -     } else if (allow)\n> > +     } else if (allow == 1) {\n> >               flags |= ALLOW_EMPTY;\n> > -     if (!opts->no_commit) {\n> > +     } else if (allow == 2) {\n> > +             drop_commit = 1;\n> > +             fprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n>\n> nit pick - usually messages start with a lowercase letter. Would it be\n> helpful to explicitly state which commit is being dropped as well as\n> why? Something like\n>    dropping <oid> <subject> - patch contents already upstream\n\nI was actually just trying to mimic the am-backend here, and copied\nits message verbatim for this case (see am_run() in builtin/am.c).\nHowever, your version does seem more helpful and informative.  I'll\nlook into it implementing it here.\n\n>\n> > +     } // else allow == 0 and there's nothing special to do\n>\n> We don't use // for comments\n\nOops, sorry.  Will fix.\n\n>\n> > +     if (!opts->no_commit && !drop_commit) {\n> >               if (author || command == TODO_REVERT || (flags & AMEND_MSG))\n> >                       res = do_commit(r, msg_file, author, opts, flags);\n> >               else\n> > @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)\n> >               if (file_exists(rebase_path_reschedule_failed_exec()))\n> >                       opts->reschedule_failed_exec = 1;\n> >\n> > +             if (file_exists(rebase_path_drop_redundant_commits()))\n> > +                     opts->drop_redundant_commits = 1;\n> > +\n> > +             if (file_exists(rebase_path_keep_redundant_commits()))\n> > +                     opts->keep_redundant_commits = 1;\n> > +\n> >               read_strategy_opts(opts, &buf);\n> >               strbuf_release(&buf);\n> >\n> > @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n> >               write_file(rebase_path_gpg_sign_opt(), \"-S%s\\n\", opts->gpg_sign);\n> >       if (opts->signoff)\n> >               write_file(rebase_path_signoff(), \"--signoff\\n\");\n> > +     if (opts->drop_redundant_commits)\n> > +             write_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n> > +     if (opts->keep_redundant_commits)\n> > +             write_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n> >       if (opts->reschedule_failed_exec)\n> >               write_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n> >\n> > diff --git a/sequencer.h b/sequencer.h\n> > index c165e0ff25..3b0ab9141f 100644\n> > --- a/sequencer.h\n> > +++ b/sequencer.h\n> > @@ -39,6 +39,7 @@ struct replay_opts {\n> >       int allow_rerere_auto;\n> >       int allow_empty;\n> >       int allow_empty_message;\n> > +     int drop_redundant_commits;\n> >       int keep_redundant_commits;\n> >       int verbose;\n> >       int quiet;\n> > diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n> > index 22d97e143b..dcb4cb4751 100755\n> > --- a/t/t3424-rebase-empty.sh\n> > +++ b/t/t3424-rebase-empty.sh\n> > @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '\n> >       git commit -m \"Five letters ought to be enough for anybody\"\n> >   '\n> >\n> > -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n> > +test_expect_failure 'rebase (am-backend)' '\n> >       test_when_finished \"git rebase --abort\" &&\n> >       git checkout -B testing localmods &&\n> >       # rebase (--am) should not drop commits that start empty\n> > @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n> >       test_cmp expect actual\n> >   '\n> >\n> > -test_expect_failure 'rebase --merge with a variety of empty commits' '\n> > -     test_when_finished \"git rebase --abort\" &&\n> > +test_expect_success 'rebase --merge --empty=drop' '\n> >       git checkout -B testing localmods &&\n> > -     # rebase --merge should not halt on the commit that becomes empty\n> > -     git rebase --merge upstream &&\n> > +     git rebase --merge --empty=drop upstream &&\n> > +\n> > +     test_write_lines D C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase --merge --empty=keep' '\n> > +     git checkout -B testing localmods &&\n> > +     git rebase --merge --empty=keep upstream &&\n> > +\n> > +     test_write_lines D C2 C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase --merge --empty=ask' '\n> > +     git checkout -B testing localmods &&\n> > +     test_must_fail git rebase --merge --empty=ask upstream &&\n> > +\n> > +     git rebase --skip &&\n> >\n> >       test_write_lines D C B A >expect &&\n> >       git log --format=%s >actual &&\n> > @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '\n> >\n> >   GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n> >\n> > -test_expect_success 'rebase --interactive with a variety of empty commits' '\n> > +test_expect_success 'rebase --interactive --empty=drop' '\n> > +     git checkout -B testing localmods &&\n> > +     git rebase --interactive --empty=drop upstream &&\n> > +\n> > +     test_write_lines D C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase --interactive --empty=keep' '\n> > +     git checkout -B testing localmods &&\n> > +     git rebase --interactive --empty=keep upstream &&\n> > +\n> > +     test_write_lines D C2 C B A >expect &&\n> > +     git log --format=%s >actual &&\n> > +     test_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase --interactive --empty=ask' '\n> >       git checkout -B testing localmods &&\n> > -     test_must_fail git rebase --interactive upstream &&\n> > +     test_must_fail git rebase --interactive --empty=ask upstream &&\n> >\n> >       git rebase --skip &&\n>\n> As the default if --empty is not given is supposed to vary depending on\n> the other options given it would be good to test that I think\n\nI'll add some tests.\n\n\nThanks for the thorough review!\nElijah\n"},{"id":"391835","messageId":"3ea48d53940cb7e1a82a8a6eb819497c0448af6a.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:22Z","receivedAt":"2020-02-15T21:36:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCommit b00bf1c9a8dd (\"git-rebase: make --allow-empty-message the\ndefault\", 2018-06-27) made --allow-empty-message the default and thus\nturned --allow-empty-message into a no-op but did not update the\ndocumentation to reflect this.  Update the documentation now, and hide\nthe option from the normal -h output since it is not useful.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt |  7 ++++---\n builtin/rebase.c             | 12 +++++++-----\n 2 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 0c4f038dd60..c83be7ffc20 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.\n See also INCOMPATIBLE OPTIONS below.\n \n --allow-empty-message::\n-\tBy default, rebasing commits with an empty message will fail.\n-\tThis option overrides that behavior, allowing commits with empty\n-\tmessages to be rebased.\n+\tNo-op.  Rebasing commits with an empty message used to fail\n+\tand this option would override that behavior, allowing commits\n+\twith empty messages to be rebased.  Now commits with an empty\n+\tmessage do not cause rebasing to halt.\n +\n See also INCOMPATIBLE OPTIONS below.\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 8081741f8aa..faa4e0d4065 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n \t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n-\t\tOPT_BOOL(0, \"allow-empty-message\", &opts.allow_empty_message,\n-\t\t\t N_(\"allow commits with empty messages\")),\n+\t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n+\t\t\t   N_(\"allow commits with empty messages\"),\n+\t\t\t   PARSE_OPT_HIDDEN),\n \t\tOPT_BOOL(0, \"rebase-merges\", &opts.rebase_merges, N_(\"rebase merge commits\")),\n \t\tOPT_BOOL(0, \"rebase-cousins\", &opts.rebase_cousins,\n \t\t\t N_(\"keep original branch points of cousins\")),\n@@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING_LIST('x', \"exec\", &exec, N_(\"exec\"),\n \t\t\t\tN_(\"add exec lines after each commit of the \"\n \t\t\t\t   \"editable list\")),\n-\t\tOPT_BOOL(0, \"allow-empty-message\",\n-\t\t\t &options.allow_empty_message,\n-\t\t\t N_(\"allow rebasing commits with empty messages\")),\n+\t\tOPT_BOOL_F(0, \"allow-empty-message\",\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\t\tN_(\"try to rebase merges instead of skipping them\"),\n-- \ngitgitgadget\n\n"},{"id":"391836","messageId":"10fdd162a05b6e76a02f9ba9f2e00b972dba31e6.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 02/20] t3404: directly test the behavior of interest","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:23Z","receivedAt":"2020-02-15T21:36:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3404.3 is a simple test added by commit d078c3910689 (\"t3404: todo list\nwith commented-out commands only aborts\", 2018-08-10) which was designed\nto test a todo list that only contained commented-out commands.  There\nwere two problems with this test: (1) its title did not reflect the\npurpose of the test, and (2) it tested the desired behavior through a\nside-effect of other functionality instead of directly testing the\ndesired behavior discussed in the commit message.\n\nModify the test to directly test the desired behavior and update the\ntest title.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3404-rebase-interactive.sh | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ae6e55ce79a..c41531f3490 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -72,15 +72,16 @@ test_expect_success 'rebase --keep-empty' '\n \ttest_line_count = 6 actual\n '\n \n-test_expect_success 'rebase -i with empty HEAD' '\n+test_expect_success 'rebase -i with empty todo list' '\n \tcat >expect <<-\\EOF &&\n \terror: nothing to do\n \tEOF\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail env FAKE_LINES=\"1 exec_true\" \\\n-\t\t\tgit rebase -i HEAD^ >actual 2>&1\n+\t\ttest_must_fail env FAKE_LINES=\"#\" \\\n+\t\t\tgit rebase -i HEAD^ >output 2>&1\n \t) &&\n+\ttail -n 1 output >actual &&  # Ignore output about changing todo list\n \ttest_i18ncmp expect actual\n '\n \n-- \ngitgitgadget\n\n"},{"id":"391838","messageId":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com","subject":"[PATCH v5 00/20] rebase: make the default backend configurable and change the default","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:21Z","receivedAt":"2020-02-15T21:36:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is v5 of en/rebase-backend based on v2.25.0. This series does a lot of\nwork around making the default rebase backend configurable, and switches the\ndefault from the apply backend to the merge one.\n\nChanges since v4:\n\n * Added a note about an unfortunately-timed Ctrl-C to the docs, as\n   suggested by SZEDER.\n * Addressed feedback from Phillip\n * Added a new patch at the end renaming the two backends. PLEASE take a\n   look at its commit message for rationale and let me know if you agree\n   with it.\n\nNote that in particular, the last patch at the end could have been partially\nsquashed into other patches, but if people objected to the patch in general\nI wanted Junio to be able to take the rest of the series and just drop that\npatch.\n\nAs with v4, there are some things mentioned earlier that are not addressed:\n\n * I did not remove the --am (now --apply) option as suggested by Phillip,\n   since Junio and Phillip were still discussing whether it is\n   wanted/needed. I kind of like having it, but have no strong opinion.\n * I noted the post-commit hook in the differences between backends. Emily\n   is investigating what changes need to happen there, so I merely\n   documented the existing differences.\n * I did not address the last two items Jonathan brought up as I couldn't\n   find enough information to reproduce or understand the problems.\n\nElijah Newren (20):\n  git-rebase.txt: update description of --allow-empty-message\n  t3404: directly test the behavior of interest\n  rebase (interactive-backend): make --keep-empty the default\n  rebase (interactive-backend): fix handling of commits that become\n    empty\n  t3406: simplify an already simple test\n  rebase, sequencer: remove the broken GIT_QUIET handling\n  rebase: make sure to pass along the quiet flag to the sequencer\n  rebase: fix handling of restrict_revision\n  t3432: make these tests work with either am or merge backends\n  rebase: allow more types of rebases to fast-forward\n  git-rebase.txt: add more details about behavioral differences of\n    backends\n  rebase: move incompatibility checks between backend options a bit\n    earlier\n  rebase: add an --am option\n  git-prompt: change the prompt for interactive-based rebases\n  rebase: drop '-i' from the reflog for interactive-based rebases\n  rebase tests: mark tests specific to the am-backend with --am\n  rebase tests: repeat some tests using the merge backend instead of am\n  rebase: make the backend configurable via config setting\n  rebase: change the default backend from \"am\" to \"merge\"\n  rebase: rename the two primary rebase backends\n\n Documentation/config/rebase.txt        |   6 +\n Documentation/git-rebase.txt           | 170 +++++++++++++---\n builtin/rebase.c                       | 257 +++++++++++++++++--------\n contrib/completion/git-prompt.sh       |   6 +-\n rebase-interactive.c                   |   7 +-\n rebase-interactive.h                   |   2 +-\n sequencer.c                            |  82 +++++---\n sequencer.h                            |   3 +-\n t/t3400-rebase.sh                      |  40 +++-\n t/t3401-rebase-and-am-rename.sh        |   4 +-\n t/t3404-rebase-interactive.sh          |  19 +-\n t/t3406-rebase-message.sh              |  19 +-\n t/t3407-rebase-abort.sh                |   6 +-\n t/t3420-rebase-autostash.sh            |  22 +--\n t/t3421-rebase-topology-linear.sh      |  48 ++---\n t/t3424-rebase-empty.sh                | 126 ++++++++++++\n t/t3425-rebase-topology-merges.sh      |   8 +-\n t/t3427-rebase-subtree.sh              |  12 +-\n t/t3432-rebase-fast-forward.sh         |  54 +++---\n t/t5407-post-rewrite-hook.sh           |  16 +-\n t/t5520-pull.sh                        |  27 ++-\n t/t6047-diff3-conflict-markers.sh      |  13 +-\n t/t7512-status-help.sh                 |  12 +-\n t/t9106-git-svn-commit-diff-clobber.sh |   3 +-\n t/t9903-bash-prompt.sh                 |   8 +-\n 25 files changed, 693 insertions(+), 277 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\n\nbase-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5\nPull-Request: https://github.com/git/git/pull/679\n\nRange-diff vs v4:\n\n  1:  3ea48d53940 =  1:  3ea48d53940 git-rebase.txt: update description of --allow-empty-message\n  2:  10fdd162a05 =  2:  10fdd162a05 t3404: directly test the behavior of interest\n  3:  179f82ab83e !  3:  74cab9a32b3 rebase (interactive-backend): make --keep-empty the default\n     @@ -4,13 +4,13 @@\n      \n          Different rebase backends have different treatment for commits which\n          start empty (i.e. have no changes relative to their parent), and the\n     -    --keep-empty option was added at some point to allow adjusting behavior\n     -    for the interactive backend.  The handling of commits which start empty\n     -    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n     -    --allow-empty-message the default, 2018-06-27), which pointed out that\n     -    the behavior for various backends is often more happenstance than\n     -    design.  The specific change made in that commit is actually quite\n     -    relevant as well and much of the logic there directly applies here.\n     +    --keep-empty option was added at some point to allow adjusting behavior.\n     +    The handling of commits which start empty is actually quite similar to\n     +    commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,\n     +    2018-06-27), which pointed out that the behavior for various backends is\n     +    often more happenstance than design.  The specific change made in that\n     +    commit is actually quite relevant as well and much of the logic there\n     +    directly applies here.\n      \n          It makes a lot of sense in 'git commit' to error out on the creation of\n          empty commits, unless an override flag is provided.  However, once\n     @@ -405,8 +405,6 @@\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n     -+\n      +test_expect_success 'rebase --interactive with a variety of empty commits' '\n      +\tgit checkout -B testing localmods &&\n      +\ttest_must_fail git rebase --interactive upstream &&\n  4:  c9542a2abe0 !  4:  54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty\n     @@ -98,6 +98,8 @@\n      +\tWith ask (implied by --interactive), the rebase will halt when\n      +\tan empty commit is applied allowing you to choose whether to\n      +\tdrop it, edit files more, or just commit the empty changes.\n     ++\tOther options, like --exec, will use the default of drop unless\n     ++\t-i/--interactive is explicitly specified.\n      ++\n      +Note that commits which start empty are kept, and commits which are\n      +clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n     @@ -224,7 +226,7 @@\n       \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n       \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n      +\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n     -+\t\t\t       N_(\"how to handle empty commits\"),\n     ++\t\t\t       N_(\"how to handle commits that become empty\"),\n      +\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n       \t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n       \t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n     @@ -342,8 +344,10 @@\n      -\tif (!opts->no_commit) {\n      +\t} else if (allow == 2) {\n      +\t\tdrop_commit = 1;\n     -+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n     -+\t} // else allow == 0 and there's nothing special to do\n     ++\t\tfprintf(stderr,\n     ++\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n     ++\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n     ++\t} /* else allow == 0 and there's nothing special to do */\n      +\tif (!opts->no_commit && !drop_commit) {\n       \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n       \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n     @@ -404,9 +408,7 @@\n      -test_expect_failure 'rebase --merge with a variety of empty commits' '\n      -\ttest_when_finished \"git rebase --abort\" &&\n      +test_expect_success 'rebase --merge --empty=drop' '\n     - \tgit checkout -B testing localmods &&\n     --\t# rebase --merge should not halt on the commit that becomes empty\n     --\tgit rebase --merge upstream &&\n     ++\tgit checkout -B testing localmods &&\n      +\tgit rebase --merge --empty=drop upstream &&\n      +\n      +\ttest_write_lines D C B A >expect &&\n     @@ -414,6 +416,17 @@\n      +\ttest_cmp expect actual\n      +'\n      +\n     ++test_expect_success 'rebase --merge uses default of --empty=drop' '\n     + \tgit checkout -B testing localmods &&\n     +-\t# rebase --merge should not halt on the commit that becomes empty\n     + \tgit rebase --merge upstream &&\n     + \n     + \ttest_write_lines D C B A >expect &&\n     +@@\n     + \ttest_cmp expect actual\n     + '\n     + \n     +-test_expect_success 'rebase --interactive with a variety of empty commits' '\n      +test_expect_success 'rebase --merge --empty=keep' '\n      +\tgit checkout -B testing localmods &&\n      +\tgit rebase --merge --empty=keep upstream &&\n     @@ -428,14 +441,12 @@\n      +\ttest_must_fail git rebase --merge --empty=ask upstream &&\n      +\n      +\tgit rebase --skip &&\n     - \n     - \ttest_write_lines D C B A >expect &&\n     - \tgit log --format=%s >actual &&\n     -@@\n     - \n     - GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n     - \n     --test_expect_success 'rebase --interactive with a variety of empty commits' '\n     ++\n     ++\ttest_write_lines D C B A >expect &&\n     ++\tgit log --format=%s >actual &&\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n      +test_expect_success 'rebase --interactive --empty=drop' '\n      +\tgit checkout -B testing localmods &&\n      +\tgit rebase --interactive --empty=drop upstream &&\n     @@ -455,11 +466,19 @@\n      +'\n      +\n      +test_expect_success 'rebase --interactive --empty=ask' '\n     - \tgit checkout -B testing localmods &&\n     --\ttest_must_fail git rebase --interactive upstream &&\n     ++\tgit checkout -B testing localmods &&\n      +\ttest_must_fail git rebase --interactive --empty=ask upstream &&\n     - \n     - \tgit rebase --skip &&\n     ++\n     ++\tgit rebase --skip &&\n     ++\n     ++\ttest_write_lines D C B A >expect &&\n     ++\tgit log --format=%s >actual &&\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     ++test_expect_success 'rebase --interactive uses default of --empty=ask' '\n     + \tgit checkout -B testing localmods &&\n     + \ttest_must_fail git rebase --interactive upstream &&\n       \n      \n       diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n  5:  9f66229d5cc =  5:  f5170e71510 t3406: simplify an already simple test\n  6:  8d731fa39c3 =  6:  2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling\n  7:  b6b6597eef8 =  7:  3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer\n  8:  0acefa988b1 =  8:  4e6f5ce8c46 rebase: fix handling of restrict_revision\n  9:  8c5b5b51330 =  9:  bcd04ec4ded t3432: make these tests work with either am or merge backends\n 10:  b8c087d6fb1 = 10:  45eb87b36b0 rebase: allow more types of rebases to fast-forward\n 11:  b50a1741e0b = 11:  11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends\n 12:  58e6e4ffb3f = 12:  0c921919dce rebase: move incompatibility checks between backend options a bit earlier\n 13:  5478c730aca = 13:  de1b0aaca88 rebase: add an --am option\n 14:  db5e29bd818 = 14:  e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases\n 15:  413e190ac9a ! 15:  f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases\n     @@ -54,17 +54,6 @@\n       \t\t */\n       \t\treturn error(_(\"%s: Unable to write new index file\"),\n       \t\t\t_(action_name(opts)));\n     -@@\n     - \tint next = todo_list->current, offset, fd;\n     - \n     - \t/*\n     --\t * rebase -i writes \"git-rebase-todo\" without the currently executing\n     --\t * command, appending it to \"done\" instead.\n     -+\t * interactive backend writes \"git-rebase-todo\" without the currently\n     -+\t * executing command, appending it to \"done\" instead.\n     - \t */\n     - \tif (is_rebase_i(opts))\n     - \t\tnext++;\n      @@\n       \t\treturn error(_(\"illegal label name: '%.*s'\"), len, name);\n       \n 16:  170be283a85 ! 16:  56486d4d694 rebase tests: mark tests specific to the am-backend with --am\n     @@ -173,6 +173,163 @@\n       testrebase \" --interactive\" .git/rebase-merge\n       \n      \n     + diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n     + --- a/t/t3421-rebase-topology-linear.sh\n     + +++ b/t/t3421-rebase-topology-linear.sh\n     +@@\n     + \t\ttest_linear_range 'd e' c..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_cmp_rev e HEAD\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'd e' b..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success --fork-point\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     +@@\n     + \t\ttest_linear_range 'd e' branch-b..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success --fork-point\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     +@@\n     + \t\ttest_cmp_rev e HEAD\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success --fork-point\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     +@@\n     + \t\ttest_linear_range 'd i' h..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'd' h..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'd i' f..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'd gp i' h..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'j d k l' c..\n     + \t\"\n     + }\n     +-test_run_rebase failure ''\n     ++test_run_rebase failure --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase failure -p\n     +@@\n     + \t\ttest_linear_range 'd k l' c..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'd k l' j..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'x y' c..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\ttest_linear_range 'x y' c..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase failure -p\n     +@@\n     + \t\ttest_linear_range 'x y' m..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase success -p\n     +@@\n     + \t\"\n     + }\n     + \n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase failure -p\n     +@@\n     + \t\ttest_linear_range 'x y' m..\n     + \t\"\n     + }\n     +-test_run_rebase success ''\n     ++test_run_rebase success --am\n     + test_run_rebase success -m\n     + test_run_rebase success -i\n     + test_have_prereq !REBASE_P || test_run_rebase failure -p\n     +\n       diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\n       --- a/t/t3425-rebase-topology-merges.sh\n       +++ b/t/t3425-rebase-topology-merges.sh\n 17:  1e3d4066c40 = 17:  dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am\n 18:  9b4ac83d2d0 = 18:  ba1f2699b39 rebase: make the backend configurable via config setting\n 19:  859a4a94d7f ! 19:  59faaa2f920 rebase: change the default backend from \"am\" to \"merge\"\n     @@ -13,6 +13,7 @@\n              patches incorrectly.[2]\n            * lack of access to original commits means that conflict marker\n              annotation has less information available.\n     +      * the am backend has safety problems with an ill-timed interrupt.\n      \n          Also, the merge/interactive backend have far more abilities, appear to\n          currently have a slight performance advantage[3] and have room for more\n     @@ -38,6 +39,24 @@\n       +\n       Note that a rebase merge works by replaying each commit from the working\n       branch on top of the <upstream> branch.  Because of this, when a merge\n     +@@\n     + should have the same behavior, though it is not clear which one is\n     + correct.\n     + \n     ++Interruptability\n     ++~~~~~~~~~~~~~~~~\n     ++\n     ++The am backend has safety problems with an ill-timed interrupt; if the\n     ++user presses Ctrl-C at the wrong time to try to abort the rebase, the\n     ++rebase can enter a state where it cannot be aborted with a subsequent\n     ++`git rebase --abort`.  The interactive backend does not appear to\n     ++suffer from the same shortcoming.  (See\n     ++https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for\n     ++details.)\n     ++\n     + Miscellaneous differences\n     + ~~~~~~~~~~~~~~~~~~~~~~~~~\n     + \n      \n       diff --git a/builtin/rebase.c b/builtin/rebase.c\n       --- a/builtin/rebase.c\n  -:  ----------- > 20:  ad8339aebf2 rebase: rename the two primary rebase backends\n\n-- \ngitgitgadget\n"},{"id":"391837","messageId":"f5170e71510732c5717c9f2d571ebc094890be64.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 05/20] t3406: simplify an already simple test","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:26Z","receivedAt":"2020-02-15T21:36:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen the merge backend was re-implemented on top of the interactive\nbackend, the output of rebase --merge changed a little.  This change\nallowed this test to be simplified, though it wasn't noticed until now.\nSimplify the testcase a little.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3406-rebase-message.sh | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex b393e1e9fee..0c2c569f959 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -18,11 +18,8 @@ test_expect_success 'setup' '\n '\n \n test_expect_success 'rebase -m' '\n-\tgit rebase -m master >report &&\n-\t>expect &&\n-\tsed -n -e \"/^Already applied: /p\" \\\n-\t\t-e \"/^Committed: /p\" report >actual &&\n-\ttest_cmp expect actual\n+\tgit rebase -m master >actual &&\n+\ttest_must_be_empty actual\n '\n \n test_expect_success 'rebase against master twice' '\n-- \ngitgitgadget\n\n"},{"id":"391839","messageId":"2791b818c4d84152edba16c339d15913f7a63022.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 06/20] rebase, sequencer: remove the broken GIT_QUIET handling","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:27Z","receivedAt":"2020-02-15T21:36:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe GIT_QUIET environment variable was used to signal the non-am\nbackends that the rebase should perform quietly.  The preserve-merges\nbackend does not make use of the quiet flag anywhere (other than to\nwrite out its state whenever it writes state), and this mechanism was\nbroken in the conversion from shell to C.  Since this environment\nvariable was specifically designed for scripts and the only backend that\nwould still use it is no longer a script, just gut this code.\n\nA subsequent commit will fix --quiet for the interactive/merge backend\nin a different way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 6 ++----\n sequencer.c      | 6 ++----\n 2 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 669690f9664..7551f950593 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t   opts->onto ? oid_to_hex(&opts->onto->object.oid) : \"\");\n \twrite_file(state_dir_path(\"orig-head\", opts), \"%s\",\n \t\t   oid_to_hex(&opts->orig_head));\n-\twrite_file(state_dir_path(\"quiet\", opts), \"%s\",\n-\t\t   opts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n+\tif (!(opts->flags & REBASE_NO_QUIET))\n+\t\twrite_file(state_dir_path(\"quiet\", opts), \"%s\", \"\");\n \tif (opts->flags & REBASE_VERBOSE)\n \t\twrite_file(state_dir_path(\"verbose\", opts), \"%s\", \"\");\n \tif (opts->strategy)\n@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"revisions\", opts->revisions);\n \tadd_var(&script_snippet, \"restrict_revision\", opts->restrict_revision ?\n \t\toid_to_hex(&opts->restrict_revision->object.oid) : NULL);\n-\tadd_var(&script_snippet, \"GIT_QUIET\",\n-\t\topts->flags & REBASE_NO_QUIET ? \"\" : \"t\");\n \tsq_quote_argv_pretty(&buf, opts->git_am_opts.argv);\n \tadd_var(&script_snippet, \"git_am_opt\", buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/sequencer.c b/sequencer.c\nindex fdb8f91fbce..f475d2a3b1c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2570,8 +2570,6 @@ static void write_strategy_opts(struct replay_opts *opts)\n int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\t      struct commit *onto, const char *orig_head)\n {\n-\tconst char *quiet = getenv(\"GIT_QUIET\");\n-\n \tif (head_name)\n \t\twrite_file(rebase_path_head_name(), \"%s\\n\", head_name);\n \tif (onto)\n@@ -2580,8 +2578,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \tif (orig_head)\n \t\twrite_file(rebase_path_orig_head(), \"%s\\n\", orig_head);\n \n-\tif (quiet)\n-\t\twrite_file(rebase_path_quiet(), \"%s\\n\", quiet);\n+\tif (opts->quiet)\n+\t\twrite_file(rebase_path_quiet(), \"%s\", \"\");\n \tif (opts->verbose)\n \t\twrite_file(rebase_path_verbose(), \"%s\", \"\");\n \tif (opts->strategy)\n-- \ngitgitgadget\n\n"},{"id":"391840","messageId":"bcd04ec4ded1858a6e1d2292bbc0d34ec28c2f57.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 09/20] t3432: make these tests work with either am or merge backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:30Z","receivedAt":"2020-02-15T21:36:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nt3432 had several stress tests for can_fast_forward(), whose intent was\nto ensure we were using the optimization of just fast forwarding when\npossible.  However, these tests verified that fast forwards had happened\nbased on the output that rebase printed to the terminal.  We can instead\ntest more directly that we actually fast-forwarded by checking the\nreflog, which also has the side effect of making the tests applicable\nfor the merge/interactive backend.\n\nThis change does lose the distinction between \"noop\" and \"noop-force\",\nbut as stated in commit c9efc216830f (\"t3432: test for --no-ff's\ninteraction with fast-forward\", 2019-08-27) which introduced that\ndistinction: \"These tests aren't supposed to endorse the status quo,\njust test for what we're currently doing.\".\n\nThis change does not actually run these tests with the merge/interactive\nbackend; instead this is just a preparatory commit.  A subsequent commit\nwhich fixes can_fast_forward() to work with that backend will then also\nchange t3432 to add tests of that backend as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------\n 1 file changed, 22 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 92f95b57da1..7432c0e2415 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,19 +44,15 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tcp .git/logs/HEAD expect &&\n \t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n-\t\t\t# Must check this case first, for 'is up to\n-\t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\told=\\$(wc -l <expect) &&\n+\t\t\ttest_line_count '-gt' \\$old .git/logs/HEAD\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout &&\n-\t\t\ttest_i18ngrep ! 'rebase forced' stdout\n-\t\telif test $what = noop-force\n-\t\tthen\n-\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n+\t\t\ttest_cmp expect .git/logs/HEAD\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -71,14 +67,14 @@ test_rebase_same_head_ () {\n \n changes='no changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '\n \n changes='our changes'\n test_rebase_same_head success noop same success work same\n-test_rebase_same_head success noop same success noop-force same master\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head success noop same success noop-force same --onto master... master\n-test_rebase_same_head success noop same success noop-force same --keep-base master\n-test_rebase_same_head success noop same success noop-force same --keep-base\n-test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success work same master\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n+test_rebase_same_head success noop same success work same --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base master\n+test_rebase_same_head success noop same success work same --keep-base\n+test_rebase_same_head success noop same success work same --no-fork-point\n+test_rebase_same_head success noop same success work same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same success noop-force diff --onto B B\n-test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success work diff --onto B B\n+test_rebase_same_head success noop same success work diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head success noop same success work diff --keep-base master\n test_rebase_same_head success noop same success work diff --keep-base\n-- \ngitgitgadget\n\n"},{"id":"391841","messageId":"74cab9a32b3704228cbb247975adb13ade46ff91.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 03/20] rebase (interactive-backend): make --keep-empty the default","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:24Z","receivedAt":"2020-02-15T21:36:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nDifferent rebase backends have different treatment for commits which\nstart empty (i.e. have no changes relative to their parent), and the\n--keep-empty option was added at some point to allow adjusting behavior.\nThe handling of commits which start empty is actually quite similar to\ncommit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,\n2018-06-27), which pointed out that the behavior for various backends is\noften more happenstance than design.  The specific change made in that\ncommit is actually quite relevant as well and much of the logic there\ndirectly applies here.\n\nIt makes a lot of sense in 'git commit' to error out on the creation of\nempty commits, unless an override flag is provided.  However, once\nsomeone determines that there is a rare case that merits using the\nmanual override to create such a commit, it is somewhere between\nannoying and harmful to have to take extra steps to keep such\nintentional commits around.  Granted, empty commits are quite rare,\nwhich is why handling of them doesn't get considered much and folks tend\nto defer to existing (accidental) behavior and assume there was a reason\nfor it, leading them to just add flags (--keep-empty in this case) that\nallow them to override the bad defaults.  Fix the interactive backend so\nthat --keep-empty is the default, much like we did with\n--allow-empty-message.  The am backend should also be fixed to have\n--keep-empty semantics for commits that start empty, but that is not\nincluded in this patch other than a testcase documenting the failure.\n\nNote that there was one test in t3421 which appears to have been written\nexpecting --keep-empty to not be the default as correct behavior.  This\ntest was introduced in commit 00b8be5a4d38 (\"add tests for rebasing of\nempty commits\", 2013-06-06), which was part of a series focusing on\nrebase topology and which had an interesting original cover letter at\nhttps://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/\nwhich noted\n    Your input especially appreciated on whether you agree with the\n    intent of the test cases.\nand then went into a long example about how one of the many tests added\nhad several questions about whether it was correct.  As such, I believe\nmost the tests in that series were about testing rebase topology with as\nmany different flags as possible and were not trying to state in general\nhow those flags should behave otherwise.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt      | 24 ++++++-----\n builtin/rebase.c                  | 28 +++++++++----\n rebase-interactive.c              |  7 +---\n rebase-interactive.h              |  2 +-\n sequencer.c                       | 20 ++++-----\n sequencer.h                       |  2 +-\n t/t3421-rebase-topology-linear.sh | 16 +++----\n t/t3424-rebase-empty.sh           | 70 +++++++++++++++++++++++++++++++\n t/t3427-rebase-subtree.sh         | 12 +++---\n 9 files changed, 126 insertions(+), 55 deletions(-)\n create mode 100755 t/t3424-rebase-empty.sh\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex c83be7ffc20..1d19542d79f 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.\n \tunchanged as a result.\n \n --keep-empty::\n-\tKeep the commits that do not change anything from its\n-\tparents in the result.\n+\tNo-op.  Rebasing commits that started empty (had no change\n+\trelative to their parent) used to fail and this option would\n+\toverride that behavior, allowing commits with empty changes to\n+\tbe rebased.  Now commits with no changes do not cause rebasing\n+\tto halt.\n +\n-See also INCOMPATIBLE OPTIONS below.\n+See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.\n \n --allow-empty-message::\n \tNo-op.  Rebasing commits with an empty message used to fail\n@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.\n Empty commits\n ~~~~~~~~~~~~~\n \n-The am backend drops any \"empty\" commits, regardless of whether the\n-commit started empty (had no changes relative to its parent to\n-start with) or ended empty (all changes were already applied\n-upstream in other commits).\n+The am backend unfortunately drops intentionally empty commits, i.e.\n+commits that started empty, though these are rare in practice.  It\n+also drops commits that become empty and has no option for controlling\n+this behavior.\n \n-The interactive backend drops commits by default that\n-started empty and halts if it hits a commit that ended up empty.\n-The `--keep-empty` option exists for the interactive backend to allow\n-it to keep commits that started empty.\n+The interactive backend keeps intentionally empty commits.\n+Unfortunately, it always halts whenever it runs across a commit that\n+becomes empty, even when the rebase is not explicitly interactive.\n \n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex faa4e0d4065..537b3241ce3 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -77,7 +77,6 @@ struct rebase_options {\n \tconst char *action;\n \tint signoff;\n \tint allow_rerere_autoupdate;\n-\tint keep_empty;\n \tint autosquash;\n \tchar *gpg_sign_opt;\n \tint autostash;\n@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \n \tgit_config_get_bool(\"rebase.abbreviatecommands\", &abbreviate_commands);\n \n-\tflags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;\n \tflags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;\n \tflags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;\n \tflags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;\n@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \treturn ret;\n }\n \n+static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n+\t\t\t\tint unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_INTERACTIVE;\n+\treturn 0;\n+}\n+\n static const char * const builtin_rebase_interactive_usage[] = {\n \tN_(\"git rebase--interactive [<options>]\"),\n \tNULL\n@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_NEGBIT(0, \"ff\", &opts.flags, N_(\"allow fast-forward\"),\n \t\t\t   REBASE_FORCE),\n-\t\tOPT_BOOL(0, \"keep-empty\", &opts.keep_empty, N_(\"keep empty commits\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL_F(0, \"allow-empty-message\", &opts.allow_empty_message,\n \t\t\t   N_(\"allow commits with empty messages\"),\n \t\t\t   PARSE_OPT_HIDDEN),\n@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\topts->allow_rerere_autoupdate ?\n \t\t\topts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?\n \t\t\t\"--rerere-autoupdate\" : \"--no-rerere-autoupdate\" : \"\");\n-\tadd_var(&script_snippet, \"keep_empty\", opts->keep_empty ? \"yes\" : \"\");\n \tadd_var(&script_snippet, \"autosquash\", opts->autosquash ? \"t\" : \"\");\n \tadd_var(&script_snippet, \"gpg_sign_opt\", opts->gpg_sign_opt);\n \tadd_var(&script_snippet, \"cmd\", opts->cmd);\n@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n-\t\tOPT_BOOL('k', \"keep-empty\", &options.keep_empty,\n-\t\t\t N_(\"preserve empty commits during rebase\")),\n+\t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n+\t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n+\t\t\tparse_opt_keep_empty },\n \t\tOPT_BOOL(0, \"autosquash\", &options.autosquash,\n \t\t\t N_(\"move commits that begin with \"\n \t\t\t    \"squash!/fixup! under -i\")),\n@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n-\tif (options.keep_empty)\n-\t\timply_interactive(&options, \"--keep-empty\");\n-\n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b72..7e7b4061bfb 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)\n \treturn MISSING_COMMIT_CHECK_IGNORE;\n }\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf)\n {\n@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,\n \t\t\t\"the rebase will be aborted.\\n\\n\");\n \n \tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n-\n-\tif (!keep_empty) {\n-\t\tmsg = _(\"Note that empty commits are commented out\");\n-\t\tstrbuf_add_commented_lines(buf, msg, strlen(msg));\n-\t}\n }\n \n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311a..05354ca3417 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -5,7 +5,7 @@ struct strbuf;\n struct repository;\n struct todo_list;\n \n-void append_todo_help(unsigned keep_empty, int command_count,\n+void append_todo_help(int command_count,\n \t\t      const char *shortrevisions, const char *shortonto,\n \t\t      struct strbuf *buf);\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\ndiff --git a/sequencer.c b/sequencer.c\nindex b9dbf1adb07..c21fc202b1c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,\n \t\t       struct replay_opts *opts,\n \t\t       struct commit *commit)\n {\n-\tint index_unchanged, empty_commit;\n+\tint index_unchanged, originally_empty;\n \n \t/*\n \t * Three cases:\n@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,\n \tif (opts->keep_redundant_commits)\n \t\treturn 1;\n \n-\tempty_commit = is_original_commit_empty(commit);\n-\tif (empty_commit < 0)\n-\t\treturn empty_commit;\n-\tif (!empty_commit)\n+\toriginally_empty = is_original_commit_empty(commit);\n+\tif (originally_empty < 0)\n+\t\treturn originally_empty;\n+\tif (!originally_empty)\n \t\treturn 0;\n \telse\n \t\treturn 1;\n@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\t\t\t   struct rev_info *revs, struct strbuf *out,\n \t\t\t\t   unsigned flags)\n {\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tint rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;\n \tint root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;\n \tstruct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;\n@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \t\tif (!to_merge) {\n \t\t\t/* non-merge commit: easy case */\n \t\t\tstrbuf_reset(&buf);\n-\t\t\tif (!keep_empty && is_empty)\n-\t\t\t\tstrbuf_addf(&buf, \"%c \", comment_line_char);\n \t\t\tstrbuf_addf(&buf, \"%s %s %s\", cmd_pick,\n \t\t\t\t    oid_to_hex(&commit->object.oid),\n \t\t\t\t    oneline.buf);\n@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \tstruct pretty_print_context pp = {0};\n \tstruct rev_info revs;\n \tstruct commit *commit;\n-\tint keep_empty = flags & TODO_LIST_KEEP_EMPTY;\n \tconst char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? \"p\" : \"pick\";\n \tint rebase_merges = flags & TODO_LIST_REBASE_MERGES;\n \n@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \t\treturn make_script_with_merges(&pp, &revs, out, flags);\n \n \twhile ((commit = get_revision(&revs))) {\n-\t\tint is_empty  = is_original_commit_empty(commit);\n+\t\tint is_empty = is_original_commit_empty(commit);\n \n \t\tif (!is_empty && (commit->object.flags & PATCHSAME))\n \t\t\tcontinue;\n-\t\tif (!keep_empty && is_empty)\n-\t\t\tstrbuf_addf(out, \"%c \", comment_line_char);\n \t\tstrbuf_addf(out, \"%s %s \", insn,\n \t\t\t    oid_to_hex(&commit->object.oid));\n \t\tpretty_print_commit(&pp, commit, out);\n@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \n \ttodo_list_to_strbuf(r, todo_list, &buf, num, flags);\n \tif (flags & TODO_LIST_APPEND_TODO_HELP)\n-\t\tappend_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),\n+\t\tappend_todo_help(count_commands(todo_list),\n \t\t\t\t shortrevisions, shortonto, &buf);\n \n \tres = write_message(buf.buf, buf.len, file, 0);\ndiff --git a/sequencer.h b/sequencer.h\nindex 9f9ae291e3c..c165e0ff254 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_skip(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-#define TODO_LIST_KEEP_EMPTY (1U << 0)\n+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */\n #define TODO_LIST_SHORTEN_IDS (1U << 1)\n #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)\n #define TODO_LIST_REBASE_MERGES (1U << 3)\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 325072b0a33..57334dca7e4 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '\n test_run_rebase () {\n \tresult=$1\n \tshift\n-\ttest_expect_$result \"rebase $* drops empty commit\" \"\n+\ttest_expect_$result \"rebase $* keeps begin-empty commits\" \"\n \t\treset_rebase &&\n-\t\tgit rebase $* c l &&\n-\t\ttest_cmp_rev c HEAD~2 &&\n-\t\ttest_linear_range 'd l' c..\n+\t\tgit rebase $* j l &&\n+\t\ttest_cmp_rev c HEAD~4 &&\n+\t\ttest_linear_range 'j d k l' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase failure ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase success -p\n+test_have_prereq !REBASE_P || test_run_rebase failure -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -230,7 +230,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n \n test_run_rebase () {\n \tresult=$1\n@@ -245,7 +245,7 @@ test_run_rebase () {\n test_run_rebase success ''\n test_run_rebase success -m\n test_run_rebase success -i\n-test_have_prereq !REBASE_P || test_run_rebase failure -p\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n test_run_rebase success --rebase-merges\n \n #       m\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nnew file mode 100755\nindex 00000000000..3b716e980e7\n--- /dev/null\n+++ b/t/t3424-rebase-empty.sh\n@@ -0,0 +1,70 @@\n+#!/bin/sh\n+\n+test_description='git rebase of commits that start or become empty'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup test repository' '\n+\ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&\n+\ttest_write_lines A B C D E F G H I J >letters &&\n+\tgit add numbers letters &&\n+\tgit commit -m A &&\n+\n+\tgit branch upstream &&\n+\tgit branch localmods &&\n+\n+\tgit checkout upstream &&\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m B &&\n+\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C &&\n+\n+\tgit checkout localmods &&\n+\ttest_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&\n+\tgit add numbers &&\n+\tgit commit -m C2 &&\n+\n+\tgit commit --allow-empty -m D &&\n+\n+\ttest_write_lines A B C D E >letters &&\n+\tgit add letters &&\n+\tgit commit -m \"Five letters ought to be enough for anybody\"\n+'\n+\n+test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tgit checkout -B testing localmods &&\n+\t# rebase (--am) should not drop commits that start empty\n+\tgit rebase upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_failure 'rebase --merge with a variety of empty commits' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tgit checkout -B testing localmods &&\n+\t# rebase --merge should not halt on the commit that becomes empty\n+\tgit rebase --merge upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive with a variety of empty commits' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --interactive upstream &&\n+\n+\tgit rebase --skip &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex bec48e6a1f9..8dceef61cff 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n-\tgit rebase --continue &&\n+\tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n \tverbose test \"$(commit_message HEAD~)\" = \"files_subtree/master5\" &&\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n-- \ngitgitgadget\n\n"},{"id":"391842","messageId":"4e6f5ce8c46253ae0eb6afddcdc513b8ff57da17.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 08/20] rebase: fix handling of restrict_revision","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:29Z","receivedAt":"2020-02-15T21:36:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nrestrict_revision in the original shell script was an excluded revision\nrange.  It is also treated that way by the am-backend.  In the\nconversion from shell to C (see commit 6ab54d17be3f (\"rebase -i:\nimplement the logic to initialize $revisions in C\", 2018-08-28)), the\ninteractive-backend accidentally treated it as a positive revision\nrather than a negated one.\n\nThis was missed as there were no tests in the testsuite that tested an\ninteractive rebase with fork-point behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  |  4 ++--\n t/t3400-rebase.sh | 20 +++++++++++++++++++-\n 2 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 1a664137d29..8264a9243f1 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \n \targv_array_pushl(&make_script_args, \"\", revisions, NULL);\n \tif (opts->restrict_revision)\n-\t\targv_array_push(&make_script_args,\n-\t\t\t\toid_to_hex(&opts->restrict_revision->object.oid));\n+\t\targv_array_pushf(&make_script_args, \"^%s\",\n+\t\t\t\t oid_to_hex(&opts->restrict_revision->object.oid));\n \n \tret = sequencer_make_script(the_repository, &todo_list.buf,\n \t\t\t\t    make_script_args.argc, make_script_args.argv,\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 79762b989a4..71fd6396cdc 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '\n \tgit rebase master\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '\n \tgit checkout -b default-base master &&\n \tgit checkout -b default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual &&\n+\tgit checkout default-base &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout default &&\n+\tgit rebase --merge &&\n+\tgit rev-parse --verify default-base >expect &&\n+\tgit rev-parse default~1 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+\tgit checkout -B default-base master &&\n+\tgit checkout -B default topic &&\n+\tgit config branch.default.remote . &&\n+\tgit config branch.default.merge refs/heads/default-base &&\n \tgit rebase &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n-- \ngitgitgadget\n\n"},{"id":"391844","messageId":"54b745c4ab5337a4f7479388c15214117915017c.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 04/20] rebase (interactive-backend): fix handling of commits that become empty","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:25Z","receivedAt":"2020-02-15T21:36:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAs established in the previous commit and commit b00bf1c9a8dd\n(git-rebase: make --allow-empty-message the default, 2018-06-27), the\nbehavior for rebase with different backends in various edge or corner\ncases is often more happenstance than design.  This commit addresses\nanother such corner case: commits which \"become empty\".\n\nA careful reader may note that there are two types of commits which would\nbecome empty due to a rebase:\n\n  * [clean cherry-pick] Commits which are clean cherry-picks of upstream\n    commits, as determined by `git log --cherry-mark ...`.  Re-applying\n    these commits would result in an empty set of changes and a\n    duplicative commit message; i.e. these are commits that have\n    \"already been applied\" upstream.\n\n  * [become empty] Commits which are not empty to start, are not clean\n    cherry-picks of upstream commits, but which still become empty after\n    being rebased.  This happens e.g. when a commit has changes which\n    are a strict subset of the changes in an upstream commit, or when\n    the changes of a commit can be found spread across or among several\n    upstream commits.\n\nClearly, in both cases the changes in the commit in question are found\nupstream already, but the commit message may not be in the latter case.\n\nWhen cherry-mark can determine a commit is already upstream, then\nbecause of how cherry-mark works this means the upstream commit message\nwas about the *exact* same set of changes.  Thus, the commit messages\ncan be assumed to be fully interchangeable (and are in fact likely to be\ncompletely identical).  As such, the clean cherry-pick case represents a\ncase when there is no information to be gained by keeping the extra\ncommit around.  All rebase types have always dropped these commits, and\nno one to my knowledge has ever requested that we do otherwise.\n\nFor many of the become empty cases (and likely even most), we will also\nbe able to drop the commit without loss of information -- but this isn't\nquite always the case.  Since these commits represent cases that were\nnot clean cherry-picks, there is no upstream commit message explaining\nthe same set of changes.  Projects with good commit message hygiene will\nlikely have the explanation from our commit message contained within or\nspread among the relevant upstream commits, but not all projects run\nthat way.  As such, the commit message of the commit being rebased may\nhave reasoning that suggests additional changes that should be made to\nadapt to the new base, or it may have information that someone wants to\nadd as a note to another commit, or perhaps someone even wants to create\nan empty commit with the commit message as-is.\n\nJunio commented on the \"become-empty\" types of commits as follows[1]:\n\n    WRT a change that ends up being empty (as opposed to a change that\n    is empty from the beginning), I'd think that the current behaviour\n    is desireable one.  \"am\" based rebase is solely to transplant an\n    existing history and want to stop much less than \"interactive\" one\n    whose purpose is to polish a series before making it publishable,\n    and asking for confirmation (\"this has become empty--do you want to\n    drop it?\") is more appropriate from the workflow point of view.\n\n[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/\n\nI would simply add that his arguments for \"am\"-based rebases actually\napply to all non-explicitly-interactive rebases.  Also, since we are\nstating that different cases should have different defaults, it may be\nworth providing a flag to allow users to select which behavior they want\nfor these commits.\n\nIntroduce a new command line flag for selecting the desired behavior:\n    --empty={drop,keep,ask}\nwith the definitions:\n    drop: drop commits which become empty\n    keep: keep commits which become empty\n    ask:  provide the user a chance to interact and pick what to do with\n          commits which become empty on a case-by-case basis\n\nIn line with Junio's suggestion, if the --empty flag is not specified,\npick defaults as follows:\n    explicitly interactive: ask\n    otherwise: drop\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 29 ++++++++++++++--\n builtin/rebase.c             | 52 ++++++++++++++++++++++++++++\n sequencer.c                  | 50 +++++++++++++++++++++------\n sequencer.h                  |  1 +\n t/t3424-rebase-empty.sh      | 66 +++++++++++++++++++++++++++++++++---\n t/t3427-rebase-subtree.sh    |  8 ++---\n 6 files changed, 183 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 1d19542d79f..e1c6f918013 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,24 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--empty={drop,keep,ask}::\n+\tHow to handle commits that are not empty to start and are not\n+\tclean cherry-picks of any upstream commit, but which become\n+\tempty after rebasing (because they contain a subset of already\n+\tupstream changes).  With drop (the default), commits that\n+\tbecome empty are dropped.  With keep, such commits are kept.\n+\tWith ask (implied by --interactive), the rebase will halt when\n+\tan empty commit is applied allowing you to choose whether to\n+\tdrop it, edit files more, or just commit the empty changes.\n+\tOther options, like --exec, will use the default of drop unless\n+\t-i/--interactive is explicitly specified.\n++\n+Note that commits which start empty are kept, and commits which are\n+clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n+always dropped.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --keep-empty::\n \tNo-op.  Rebasing commits that started empty (had no change\n \trelative to their parent) used to fail and this option would\n@@ -561,6 +579,7 @@ are incompatible with the following options:\n  * --interactive\n  * --exec\n  * --keep-empty\n+ * --empty=\n  * --edit-todo\n  * --root when used in combination with --onto\n \n@@ -569,6 +588,7 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --interactive\n  * --preserve-merges and --signoff\n  * --preserve-merges and --rebase-merges\n+ * --preserve-merges and --empty=\n  * --keep-base and --onto\n  * --keep-base and --root\n \n@@ -585,9 +605,12 @@ commits that started empty, though these are rare in practice.  It\n also drops commits that become empty and has no option for controlling\n this behavior.\n \n-The interactive backend keeps intentionally empty commits.\n-Unfortunately, it always halts whenever it runs across a commit that\n-becomes empty, even when the rebase is not explicitly interactive.\n+The interactive backend keeps intentionally empty commits.  Similar to\n+the am backend, by default the interactive backend drops commits that\n+become empty unless -i/--interactive is specified (in which case it\n+stops and asks the user what to do).  The interactive backend also has\n+an --empty={drop,keep,ask} option for changing the behavior of\n+handling commits that become empty.\n \n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 537b3241ce3..669690f9664 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -50,8 +50,16 @@ enum rebase_type {\n \tREBASE_PRESERVE_MERGES\n };\n \n+enum empty_type {\n+\tEMPTY_UNSPECIFIED = -1,\n+\tEMPTY_DROP,\n+\tEMPTY_KEEP,\n+\tEMPTY_ASK\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n+\tenum empty_type empty;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -91,6 +99,7 @@ struct rebase_options {\n \n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n+\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n \treplay.allow_empty = 1;\n \treplay.allow_empty_message = opts->allow_empty_message;\n+\treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n+\treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n \n \tBUG_ON_OPT_ARG(arg);\n \n+\t/*\n+\t * If we ever want to remap --keep-empty to --empty=keep, insert:\n+\t * \topts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n+\t */\n \topts->type = REBASE_INTERACTIVE;\n \treturn 0;\n }\n@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+static enum empty_type parse_empty_value(const char *value)\n+{\n+\tif (!strcasecmp(value, \"drop\"))\n+\t\treturn EMPTY_DROP;\n+\telse if (!strcasecmp(value, \"keep\"))\n+\t\treturn EMPTY_KEEP;\n+\telse if (!strcasecmp(value, \"ask\"))\n+\t\treturn EMPTY_ASK;\n+\n+\tdie(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n+}\n+\n+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *options = opt->value;\n+\tenum empty_type value = parse_empty_value(arg);\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\toptions->empty = value;\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@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t\t \"ignoring them\"),\n \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n+\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n+\t\t\t       N_(\"how to handle commits that become empty\"),\n+\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n \t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n \t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (!(options.flags & REBASE_NO_QUIET))\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n+\tif (options.empty != EMPTY_UNSPECIFIED)\n+\t\timply_interactive(&options, \"--empty\");\n+\n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n \t\toptions.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \t}\n \n+\tif (options.empty == EMPTY_UNSPECIFIED) {\n+\t\tif (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n+\t\t\toptions.empty = EMPTY_ASK;\n+\t\telse if (exec.nr > 0)\n+\t\t\toptions.empty = EMPTY_KEEP;\n+\t\telse\n+\t\t\toptions.empty = EMPTY_DROP;\n+\t}\n \tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\ndiff --git a/sequencer.c b/sequencer.c\nindex c21fc202b1c..fdb8f91fbce 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n \n static int git_sequencer_config(const char *k, const char *v, void *cb)\n {\n@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)\n }\n \n /*\n- * Do we run \"git commit\" with \"--allow-empty\"?\n+ * Should empty commits be allowed?  Return status:\n+ *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)\n+ *     0: Halt on empty commit\n+ *     1: Allow empty commit\n+ *     2: Drop empty commit\n  */\n static int allow_empty(struct repository *r,\n \t\t       struct replay_opts *opts,\n@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,\n \tint index_unchanged, originally_empty;\n \n \t/*\n-\t * Three cases:\n+\t * Four cases:\n \t *\n \t * (1) we do not allow empty at all and error out.\n \t *\n-\t * (2) we allow ones that were initially empty, but\n-\t * forbid the ones that become empty;\n+\t * (2) we allow ones that were initially empty, and\n+\t *     just drop the ones that become empty\n \t *\n-\t * (3) we allow both.\n+\t * (3) we allow ones that were initially empty, but\n+\t *     halt for the ones that become empty;\n+\t *\n+\t * (4) we allow both.\n \t */\n \tif (!opts->allow_empty)\n \t\treturn 0; /* let \"git commit\" barf as necessary */\n@@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,\n \toriginally_empty = is_original_commit_empty(commit);\n \tif (originally_empty < 0)\n \t\treturn originally_empty;\n-\tif (!originally_empty)\n-\t\treturn 0;\n-\telse\n+\tif (originally_empty)\n \t\treturn 1;\n+\telse if (opts->drop_redundant_commits)\n+\t\treturn 2;\n+\telse\n+\t\treturn 0;\n }\n \n static struct {\n@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,\n \tchar *author = NULL;\n \tstruct commit_message msg = { NULL, NULL, NULL, NULL };\n \tstruct strbuf msgbuf = STRBUF_INIT;\n-\tint res, unborn = 0, reword = 0, allow;\n+\tint res, unborn = 0, reword = 0, allow, drop_commit;\n \n \tif (opts->no_commit) {\n \t\t/*\n@@ -1935,13 +1946,20 @@ static int do_pick_commit(struct repository *r,\n \t\tgoto leave;\n \t}\n \n+\tdrop_commit = 0;\n \tallow = allow_empty(r, opts, commit);\n \tif (allow < 0) {\n \t\tres = allow;\n \t\tgoto leave;\n-\t} else if (allow)\n+\t} else if (allow == 1) {\n \t\tflags |= ALLOW_EMPTY;\n-\tif (!opts->no_commit) {\n+\t} else if (allow == 2) {\n+\t\tdrop_commit = 1;\n+\t\tfprintf(stderr,\n+\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n+\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n+\t} /* else allow == 0 and there's nothing special to do */\n+\tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n \t\telse\n@@ -2495,6 +2513,12 @@ static int read_populate_opts(struct replay_opts *opts)\n \t\tif (file_exists(rebase_path_reschedule_failed_exec()))\n \t\t\topts->reschedule_failed_exec = 1;\n \n+\t\tif (file_exists(rebase_path_drop_redundant_commits()))\n+\t\t\topts->drop_redundant_commits = 1;\n+\n+\t\tif (file_exists(rebase_path_keep_redundant_commits()))\n+\t\t\topts->keep_redundant_commits = 1;\n+\n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_release(&buf);\n \n@@ -2574,6 +2598,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_gpg_sign_opt(), \"-S%s\\n\", opts->gpg_sign);\n \tif (opts->signoff)\n \t\twrite_file(rebase_path_signoff(), \"--signoff\\n\");\n+\tif (opts->drop_redundant_commits)\n+\t\twrite_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n+\tif (opts->keep_redundant_commits)\n+\t\twrite_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n \tif (opts->reschedule_failed_exec)\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \ndiff --git a/sequencer.h b/sequencer.h\nindex c165e0ff254..3b0ab9141fb 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -39,6 +39,7 @@ struct replay_opts {\n \tint allow_rerere_auto;\n \tint allow_empty;\n \tint allow_empty_message;\n+\tint drop_redundant_commits;\n \tint keep_redundant_commits;\n \tint verbose;\n \tint quiet;\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nindex 3b716e980e7..cfb1ebc1ff9 100755\n--- a/t/t3424-rebase-empty.sh\n+++ b/t/t3424-rebase-empty.sh\n@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '\n \tgit commit -m \"Five letters ought to be enough for anybody\"\n '\n \n-test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n+test_expect_failure 'rebase (am-backend)' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tgit checkout -B testing localmods &&\n \t# rebase (--am) should not drop commits that start empty\n@@ -45,10 +45,17 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n \ttest_cmp expect actual\n '\n \n-test_expect_failure 'rebase --merge with a variety of empty commits' '\n-\ttest_when_finished \"git rebase --abort\" &&\n+test_expect_success 'rebase --merge --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=drop upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge uses default of --empty=drop' '\n \tgit checkout -B testing localmods &&\n-\t# rebase --merge should not halt on the commit that becomes empty\n \tgit rebase --merge upstream &&\n \n \ttest_write_lines D C B A >expect &&\n@@ -56,7 +63,56 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'rebase --interactive with a variety of empty commits' '\n+test_expect_success 'rebase --merge --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --merge --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --merge --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --merge --empty=ask upstream &&\n+\n+\tgit rebase --skip &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=drop' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=drop upstream &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=keep' '\n+\tgit checkout -B testing localmods &&\n+\tgit rebase --interactive --empty=keep upstream &&\n+\n+\ttest_write_lines D C2 C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive --empty=ask' '\n+\tgit checkout -B testing localmods &&\n+\ttest_must_fail git rebase --interactive --empty=ask upstream &&\n+\n+\tgit rebase --skip &&\n+\n+\ttest_write_lines D C B A >expect &&\n+\tgit log --format=%s >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --interactive uses default of --empty=ask' '\n \tgit checkout -B testing localmods &&\n \ttest_must_fail git rebase --interactive upstream &&\n \ndiff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\nindex 8dceef61cff..79e43a370ba 100755\n--- a/t/t3427-rebase-subtree.sh\n+++ b/t/t3427-rebase-subtree.sh\n@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&\n \t: first pick results in no changes &&\n \tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '\n \tverbose test \"$(commit_message HEAD)\" = \"Empty commit\"\n '\n \n-test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '\n+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '\n \treset_rebase &&\n \tgit checkout -b rebase-merges-onto to-rebase &&\n-\ttest_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&\n+\ttest_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&\n \t: first pick results in no changes &&\n \tgit rebase --skip &&\n \tverbose test \"$(commit_message HEAD~2)\" = \"master4\" &&\n-- \ngitgitgadget\n\n"},{"id":"391843","messageId":"0c921919dcee49e90825e1f42177ce58f8b3eb5f.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 12/20] rebase: move incompatibility checks between backend options a bit earlier","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:33Z","receivedAt":"2020-02-15T21:37:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 6e9a2fedc7f..a2f05f783d8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n+\tif (options.git_am_opts.argc) {\n+\t\t/* all am options except -q are compatible only with --am */\n+\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n+\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n+\t\t\t\tbreak;\n+\n+\t\tif (is_interactive(&options) && i >= 0)\n+\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t      \"interactive or merge options\"));\n+\t}\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_INTERACTIVE:\n@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (reschedule_failed_exec >= 0)\n \t\toptions.reschedule_failed_exec = reschedule_failed_exec;\n \n-\tif (options.git_am_opts.argc) {\n-\t\t/* all am options except -q are compatible only with --am */\n-\t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n-\t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-\t\t\t\tbreak;\n-\n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n-\t}\n-\n \tif (options.signoff) {\n \t\tif (options.type == REBASE_PRESERVE_MERGES)\n \t\t\tdie(\"cannot combine '--signoff' with \"\n-- \ngitgitgadget\n\n"},{"id":"391848","messageId":"45eb87b36b0dc93385a774b691da6eb185be1920.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 10/20] rebase: allow more types of rebases to fast-forward","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:31Z","receivedAt":"2020-02-15T21:37:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we dis-allowed rebases using the interactive backend from\nperforming a fast-forward to short-circuit the rebase operation.  This\nmade sense for explicitly interactive rebases and some implicitly\ninteractive rebases, but certainly became overly stringent when the\nmerge backend was re-implemented via the interactive backend.\n\nJust as the am-based rebase has always had to disable the fast-forward\nbased on a variety of conditions or flags (e.g. --signoff, --whitespace,\netc.), we need to do the same but now with a few more options.  However,\ncontinuing to use REBASE_FORCE for tracking this is problematic because\nthe interactive backend used it for a different purpose.  (When\nREBASE_FORCE wasn't set, the interactive backend would not fast-forward\nthe whole series but would fast-forward individual \"pick\" commits at the\nbeginning of the todo list, and then a squash or something would cause\nit to start generating new commits.)  So, introduce a new\nallow_preemptive_ff flag contained within cmd_rebase() and use it to\ntrack whether we are going to allow a pre-emptive fast-forward that\nshort-circuits the whole rebase.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c               | 18 ++++++++++++++----\n t/t3432-rebase-fast-forward.sh |  2 ++\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 8264a9243f1..6e9a2fedc7f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct object_id squash_onto;\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n+\tint allow_preemptive_ff = 1;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t    state_dir_base, cmd_live_rebase, buf.buf);\n \t}\n \n+\tif ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||\n+\t    (action != ACTION_NONE) ||\n+\t    (exec.nr > 0) ||\n+\t    options.autosquash) {\n+\t\tallow_preemptive_ff = 0;\n+\t}\n+\n \tfor (i = 0; i < options.git_am_opts.argc; i++) {\n \t\tconst char *option = options.git_am_opts.argv[i], *p;\n \t\tif (!strcmp(option, \"--committer-date-is-author-date\") ||\n \t\t    !strcmp(option, \"--ignore-date\") ||\n \t\t    !strcmp(option, \"--whitespace=fix\") ||\n \t\t    !strcmp(option, \"--whitespace=strip\"))\n-\t\t\toptions.flags |= REBASE_FORCE;\n+\t\t\tallow_preemptive_ff = 0;\n \t\telse if (skip_prefix(option, \"-C\", &p)) {\n \t\t\twhile (*p)\n \t\t\t\tif (!isdigit(*(p++)))\n@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t/*\n \t * Check if we are already based on onto with linear history,\n \t * in which case we could fast-forward without replacing the commits\n-\t * with new commits recreated by replaying their changes. This\n-\t * optimization must not be done if this is an interactive rebase.\n+\t * with new commits recreated by replaying their changes.\n+\t *\n+\t * Note that can_fast_forward() initializes merge_base, so we have to\n+\t * call it before checking allow_preemptive_ff.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\t    allow_preemptive_ff) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 7432c0e2415..40388ccf9f3 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -30,6 +30,8 @@ test_rebase_same_head () {\n \tshift &&\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\n \n test_rebase_same_head_ () {\n-- \ngitgitgadget\n\n"},{"id":"391845","messageId":"59faaa2f9201c794d10ee0e14ea52c5360e699ad.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 19/20] rebase: change the default backend from \"am\" to \"merge\"","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:40Z","receivedAt":"2020-02-15T21:37:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe am-backend drops information and thus limits what we can do:\n\n  * lack of full tree information from the original commits means we\n    cannot do directory rename detection and warn users that they might\n    want to move some of their new files that they placed in old\n    directories to prevent their becoming orphaned.[1]\n  * reduction in context from only having a few lines beyond those\n    changed means that when context lines are non-unique we can apply\n    patches incorrectly.[2]\n  * lack of access to original commits means that conflict marker\n    annotation has less information available.\n  * the am backend has safety problems with an ill-timed interrupt.\n\nAlso, the merge/interactive backend have far more abilities, appear to\ncurrently have a slight performance advantage[3] and have room for more\noptimizations than the am backend[4] (and work is underway to take\nadvantage of some of those possibilities).\n\n[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/\n[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/\n[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt           | 13 ++++++++++++-\n builtin/rebase.c                       |  4 ++--\n t/t5520-pull.sh                        | 10 ++++++----\n t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-\n 4 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 93092da3bf6..fbac1cf38dd 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -315,7 +315,7 @@ See also INCOMPATIBLE OPTIONS below.\n --merge::\n \tUse merging strategies to rebase.  When the recursive (default) merge\n \tstrategy is used, this allows rebase to be aware of renames on the\n-\tupstream side.\n+\tupstream side.  This is the default.\n +\n Note that a rebase merge works by replaying each commit from the working\n branch on top of the <upstream> branch.  Because of this, when a merge\n@@ -683,6 +683,17 @@ accident of implementation rather than by design.  Both backends\n should have the same behavior, though it is not clear which one is\n correct.\n \n+Interruptability\n+~~~~~~~~~~~~~~~~\n+\n+The am backend has safety problems with an ill-timed interrupt; if the\n+user presses Ctrl-C at the wrong time to try to abort the rebase, the\n+rebase can enter a state where it cannot be aborted with a subsequent\n+`git rebase --abort`.  The interactive backend does not appear to\n+suffer from the same shortcoming.  (See\n+https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for\n+details.)\n+\n Miscellaneous differences\n ~~~~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f0a862f41b5..db8fd699d79 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -101,7 +101,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"am\",\t  \t\\\n+\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\toptions.type = REBASE_MERGE;\n+\t\t\timply_interactive(&options, \"--merge\");\n \t\telse if (!strcmp(options.default_backend, \"am\"))\n \t\t\toptions.type = REBASE_AM;\n \t\telse\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 3fff6a06fa8..4f9e7f7ff6b 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '\n \ttest_tick &&\n \tgit commit -m \"Create conflict\" seq.txt &&\n \ttest_must_fail git pull --rebase . seq 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success 'failed --rebase shows advice' '\n@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '\n \tgit checkout -f -b fails-to-rebase HEAD^ &&\n \ttest_commit v2-without-cr file \"2\" file2-lf &&\n \ttest_must_fail git pull --rebase . diverging 2>err >out &&\n-\ttest_i18ngrep \"Resolve all conflicts manually\" out\n+\ttest_i18ngrep \"Resolve all conflicts manually\" err\n '\n \n test_expect_success '--rebase fails with multiple branches' '\n@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull --rebase &&\n-\t\tfind .git/rebase-apply -name \"000*\" >patches &&\n-\t\ttest_line_count = 1 patches\n+\t\tcat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&\n+\t\tgrep -v -e \\# -e ^$ work >patches &&\n+\t\ttest_line_count = 1 patches &&\n+\t\trm -f work\n \t)\n '\n \ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex dbe8deac0d2..aec45bca3b7 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' \"\n \n \n test_expect_success 'check that rebase really failed' '\n-\ttest -d .git/rebase-apply\n+\tgit status >output &&\n+\tgrep currently.rebasing output\n '\n \n test_expect_success 'resolve, continue the rebase and dcommit' \"\n-- \ngitgitgadget\n\n"},{"id":"391846","messageId":"ba1f2699b397985750cc32f638b438b27f6c2eaa.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 18/20] rebase: make the backend configurable via config setting","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:39Z","receivedAt":"2020-02-15T21:37:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt |  8 ++++++++\n builtin/rebase.c                | 31 ++++++++++++++++++++++++-------\n 2 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex d98e32d812e..e6ae30c9999 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -5,6 +5,14 @@ rebase.useBuiltin::\n \tis always used. Setting this will emit a warning, to alert any\n \tremaining users that setting this now does nothing.\n \n+rebase.backend::\n+\tDefault backend to use for rebasing.  Possible choices are\n+\t'am' or 'merge' (note that the merge backend is sometimes also\n+\trefered to as the interactive backend or the interactive\n+\tmachinery elsewhere in the docs).  Also, in the future, if the\n+\tmerge backend gains all remaining capabilities of the am\n+\tbackend, this setting may become unused.\n+\n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\n \trebase. False by default.\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ffaa7935240..f0a862f41b5 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -60,6 +60,7 @@ enum empty_type {\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n+\tconst char *default_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -100,6 +101,7 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n+\t\t.default_backend = \"am\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)\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+\n \treturn git_default_config(var, value, data);\n }\n \n@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n-\t\tif (is_interactive(&options) && i >= 0)\n-\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t      \"interactive or merge options\"));\n+\t\tif (i >= 0) {\n+\t\t\tif (is_interactive(&options))\n+\t\t\t\tdie(_(\"cannot combine am options with either \"\n+\t\t\t\t      \"interactive or merge options\"));\n+\t\t\telse\n+\t\t\t\toptions.type = REBASE_AM;\n+\t\t}\n+\t}\n+\n+\tif (options.type == REBASE_UNSPECIFIED) {\n+\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"am\"))\n+\t\t\toptions.type = REBASE_AM;\n+\t\telse\n+\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n+\t\t\t    options.default_backend);\n \t}\n \n \tswitch (options.type) {\n@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n-\t\t/* the default rebase backend is `--am` */\n-\t\toptions.type = REBASE_AM;\n-\t\toptions.state_dir = apply_dir();\n-\t\tbreak;\n+\t\tBUG(\"options.type was just set above; should be unreachable.\");\n \t}\n \n \tif (options.empty == EMPTY_UNSPECIFIED) {\n-- \ngitgitgadget\n\n"},{"id":"391847","messageId":"dc12574a8b3a9bd9afd6146354ea7d4071ab855d.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 17/20] rebase tests: repeat some tests using the merge backend instead of am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:38Z","receivedAt":"2020-02-15T21:37:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn order to ensure the merge/interactive backend gets similar coverage\nto the am one, add some tests for cases where previously only the am\nbackend was tested.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t5520-pull.sh                   | 17 +++++++++++++++--\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--\n 2 files changed, 26 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 602d996a33b..3fff6a06fa8 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -277,14 +277,27 @@ test_expect_success '--rebase' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--rebase fast forward' '\n+test_expect_success '--rebase (merge) fast forward' '\n \tgit reset --hard before-rebase &&\n \tgit checkout -b ff &&\n \techo another modification >file &&\n \tgit commit -m third file &&\n \n \tgit checkout to-rebase &&\n-\tgit pull --rebase . ff &&\n+\tgit -c rebase.backend=merge pull --rebase . ff &&\n+\ttest_cmp_rev HEAD ff &&\n+\n+\t# The above only validates the result.  Did we actually bypass rebase?\n+\tgit reflog -1 >reflog.actual &&\n+\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\ttest_cmp reflog.expected reflog.fuzzy\n+'\n+\n+test_expect_success '--rebase (am) fast forward' '\n+\tgit reset --hard before-rebase &&\n+\n+\tgit -c rebase.backend=am pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 860542aad00..d383ce81302 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n-test_expect_success 'rebase describes fake ancestor base' '\n+test_expect_success 'rebase --merge describes parent of commit being picked' '\n \ttest_create_repo rebase &&\n \t(\n \t\tcd rebase &&\n@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '\n \t\ttest_commit master file &&\n \t\tgit checkout -b side HEAD^ &&\n \t\ttest_commit side file &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&\n+\t\tgrep \"||||||| parent of\" file\n+\t)\n+'\n+\n+test_expect_success 'rebase --am describes fake ancestor base' '\n+\t(\n+\t\tcd rebase &&\n+\t\tgit rebase --abort &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\n-- \ngitgitgadget\n\n"},{"id":"391849","messageId":"56486d4d694be31c6912b7953fd7da3bc8db791f.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 16/20] rebase tests: mark tests specific to the am-backend with --am","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:37Z","receivedAt":"2020-02-15T21:37:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have many rebase tests in the testsuite, and often the same test is\nrepeated multiple times just testing different backends.  For those\ntests that were specifically trying to test the am backend, add the --am\nflag.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3400-rebase.sh                 | 10 ++++-----\n t/t3401-rebase-and-am-rename.sh   |  4 ++--\n t/t3404-rebase-interactive.sh     |  2 +-\n t/t3406-rebase-message.sh         | 12 +++++------\n t/t3407-rebase-abort.sh           |  6 +++---\n t/t3420-rebase-autostash.sh       |  2 +-\n t/t3421-rebase-topology-linear.sh | 34 +++++++++++++++----------------\n t/t3425-rebase-topology-merges.sh |  8 ++++----\n t/t3432-rebase-fast-forward.sh    |  4 ++--\n t/t5407-post-rewrite-hook.sh      | 12 +++++------\n t/t7512-status-help.sh            | 12 +++++------\n 11 files changed, 53 insertions(+), 53 deletions(-)\n\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 71fd6396cdc..0a491f23632 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase &&\n+\tgit rebase --am &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \n test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase -q master >output.out 2>&1 &&\n+\tgit rebase --am -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex a0b9438b228..50803958fd0 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase (am): directory rename detected' '\n+test_expect_failure 'rebase --am: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase A &&\n+\t\tgit -c merge.directoryRenames=true rebase --am A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a31583eb2fd..f964b2cd41d 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 0c2c569f959..7ce617fc1fb 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase master >out &&\n+\tgit rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase master >out &&\n+\tgit rebase --force-rebase --am master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase master topic >out &&\n+\tgit rebase --am master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase topic >out &&\n+\tgit rebase --am topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase reflog-onto &&\n+\tgit rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 910f2182843..3e318261703 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --quit' '\n+test_expect_success 'rebase --am --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase master &&\n+\ttest_must_fail git rebase --am master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 5f7e73cf83a..3816159e207 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \"\" .git/rebase-apply\n+testrebase \" --am\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 57334dca7e4..e12304012a2 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -26,7 +26,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -50,7 +50,7 @@ test_run_rebase () {\n \t\ttest_cmp_rev e HEAD\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -66,7 +66,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' b..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -83,7 +83,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' branch-b..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -98,7 +98,7 @@ test_run_rebase () {\n \t\ttest_cmp_rev e HEAD\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -139,7 +139,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd i' h..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -154,7 +154,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd' h..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -169,7 +169,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd i' f..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -184,7 +184,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd gp i' h..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -212,7 +212,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'j d k l' c..\n \t\"\n }\n-test_run_rebase failure ''\n+test_run_rebase failure --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -227,7 +227,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd k l' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -242,7 +242,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd k l' j..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -282,7 +282,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -297,7 +297,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' c..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -312,7 +312,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' m..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -328,7 +328,7 @@ test_run_rebase () {\n \t\"\n }\n \n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -343,7 +343,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' m..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex fd8efe84fe8..19700b025b5 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success ''\n+test_run_rebase success --am\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' ''\n+test_run_rebase success 'n o e' --am\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' ''\n+test_run_rebase success 'd n o e' --am\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 40388ccf9f3..4b3cecce560 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 7344253bfbb..a8a73616e46 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase' '\n+test_expect_success 'git rebase --am' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip' '\n+test_expect_success 'git rebase --am --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --am --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip the last one' '\n+test_expect_success 'git rebase --am --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --onto D A &&\n+\ttest_must_fail git rebase --am --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 66d7a627972..d22b0acf2ac 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase in progress before rebase --continue' '\n+test_expect_success 'status when rebase --am in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\n-- \ngitgitgadget\n\n"},{"id":"391850","messageId":"ad8339aebf28ec84c22ed59cef06614d204adb55.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:41Z","receivedAt":"2020-02-15T21:37:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nTwo related changes, with separate rationale for each:\n\nRename the 'interactive' backend to 'merge' because:\n  * 'interactive' as a name caused confusion; this backend has been used\n    for many kinds of non-interactive rebases, and will probably be used\n    in the future for more non-interactive rebases than interactive ones\n    given that we are making it the default.\n  * 'interactive' is not the underlying strategy; merging is.\n  * the directory where state is stored is not called\n    .git/rebase-interactive but .git/rebase-merge.\n\nRename the 'am' backend to 'apply' because:\n  * Few users are familiar with git-am as a reference point.\n  * Related to the above, the name 'am' makes sentences in the\n    documentation harder for users to read and comprehend (they may read\n    it as the verb from \"I am\"); avoiding this difficult places a large\n    burden on anyone writing documentation about this backend to be very\n    careful with quoting and sentence structure and often forces\n    annoying redundancy to try to avoid such problems.\n  * Users stumble over pronunciation (\"am\" as in \"I am a person not a\n    backend\" or \"am\" as in \"the first and thirteenth letters in the\n    alphabet in order are \"A-M\"); this may drive confusion when one user\n    tries to explain to another what they are doing.\n  * While \"am\" is the tool driving this backend, the tool driving git-am\n    is git-apply, and since we are driving towards lower-level tools\n    for the naming of the merge backend we may as well do so here too.\n  * The directory where state is stored has never been called\n    .git/rebase-am, it was always called .git/rebase-apply.\n\nFor all the reasons listed above:\n  * Modify the documentation to refer to the backends with the new names\n  * Provide a brief note in the documentation connecting the new names\n    to the old names in case users run across the old names anywhere\n    (e.g. in old release notes or older versions of the documentation)\n  * Change the (new) --am command line flag to --apply\n  * Rename some enums, variables, and functions to reinforce the new\n    backend names for us as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/config/rebase.txt   |  8 +--\n Documentation/git-rebase.txt      | 77 +++++++++++++-----------\n builtin/rebase.c                  | 99 +++++++++++++++----------------\n t/t3400-rebase.sh                 | 16 ++---\n t/t3401-rebase-and-am-rename.sh   |  4 +-\n t/t3404-rebase-interactive.sh     |  2 +-\n t/t3406-rebase-message.sh         | 12 ++--\n t/t3407-rebase-abort.sh           |  6 +-\n t/t3420-rebase-autostash.sh       | 22 +++----\n t/t3421-rebase-topology-linear.sh | 34 +++++------\n t/t3424-rebase-empty.sh           |  6 +-\n t/t3425-rebase-topology-merges.sh |  8 +--\n t/t3432-rebase-fast-forward.sh    |  4 +-\n t/t5407-post-rewrite-hook.sh      | 16 ++---\n t/t5520-pull.sh                   |  2 +-\n t/t6047-diff3-conflict-markers.sh |  4 +-\n t/t7512-status-help.sh            | 12 ++--\n t/t9903-bash-prompt.sh            |  2 +-\n 18 files changed, 166 insertions(+), 168 deletions(-)\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex e6ae30c9999..7f7a07d22f8 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -7,11 +7,9 @@ rebase.useBuiltin::\n \n rebase.backend::\n \tDefault backend to use for rebasing.  Possible choices are\n-\t'am' or 'merge' (note that the merge backend is sometimes also\n-\trefered to as the interactive backend or the interactive\n-\tmachinery elsewhere in the docs).  Also, in the future, if the\n-\tmerge backend gains all remaining capabilities of the am\n-\tbackend, this setting may become unused.\n+\t'apply' or 'merge'.  In the future, if the merge backend gains\n+\tall remaining capabilities of the apply backend, this setting\n+\tmay become unused.\n \n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex fbac1cf38dd..8c1f4b82680 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,10 +258,10 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n---am:\n-\tUse git-am internally to rebase.  This option may become a\n-\tno-op in the future once the interactive backend handles\n-\teverything the am one does.\n+--apply:\n+\tUse applying strategies to rebase (calling `git-am`\n+\tinternally).  This option may become a no-op in the future\n+\tonce the merge backend handles everything the apply one does.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -385,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.  Implies --am.\n+\tever ignored.  Implies --apply.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -425,7 +425,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThese flags are passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n-\tImplies --am.\n+\tImplies --apply.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -569,7 +569,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n- * --am\n+ * --apply\n  * --committer-date-is-author-date\n  * --ignore-date\n  * --ignore-whitespace\n@@ -604,42 +604,48 @@ In addition, the following pairs of options are incompatible:\n BEHAVIORAL DIFFERENCES\n -----------------------\n \n-There are some subtle differences how the backends behave.\n+git rebase has two primary backends: apply and merge.  (The apply\n+backend used to known as the 'am' backend, but the name led to\n+confusion as it looks like a verb instead of a noun.  Also, the merge\n+backend used to be known as the interactive backend, but it is now\n+used for non-interactive cases as well.  Both were renamed based on\n+lower-level functionality that underpinned each.) There are some\n+subtle differences in how these two backends behave:\n \n Empty commits\n ~~~~~~~~~~~~~\n \n-The am backend unfortunately drops intentionally empty commits, i.e.\n+The apply backend unfortunately drops intentionally empty commits, i.e.\n commits that started empty, though these are rare in practice.  It\n also drops commits that become empty and has no option for controlling\n this behavior.\n \n-The interactive backend keeps intentionally empty commits.  Similar to\n-the am backend, by default the interactive backend drops commits that\n-become empty unless -i/--interactive is specified (in which case it\n-stops and asks the user what to do).  The interactive backend also has\n-an --empty={drop,keep,ask} option for changing the behavior of\n-handling commits that become empty.\n+The merge backend keeps intentionally empty commits.  Similar to the\n+apply backend, by default the merge backend drops commits that become\n+empty unless -i/--interactive is specified (in which case it stops and\n+asks the user what to do).  The merge backend also has an\n+--empty={drop,keep,ask} option for changing the behavior of handling\n+commits that become empty.\n \n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n Due to the lack of accurate tree information (arising from\n constructing fake ancestors with the limited information available in\n-patches), directory rename detection is disabled in the am backend.\n+patches), directory rename detection is disabled in the apply backend.\n Disabled directory rename detection means that if one side of history\n renames a directory and the other adds new files to the old directory,\n then the new files will be left behind in the old directory without\n any warning at the time of rebasing that you may want to move these\n files into the new directory.\n \n-Directory rename detection works with the merge and interactive\n-backends to provide you warnings in such cases.\n+Directory rename detection works with the merge backend to provide you\n+warnings in such cases.\n \n Context\n ~~~~~~~\n \n-The am backend works by creating a sequence of patches (by calling\n+The apply backend works by creating a sequence of patches (by calling\n `format-patch` internally), and then applying the patches in sequence\n (calling `am` internally).  Patches are composed of multiple hunks,\n each with line numbers, a context region, and the actual changes.  The\n@@ -654,7 +660,7 @@ Setting diff.context to a larger value may prevent such types of\n problems, but increases the chance of spurious conflicts (since it\n will require more lines of matching context to apply).\n \n-The interactive backend works with a full copy of each relevant file,\n+The merge backend works with a full copy of each relevant file,\n insulating it from these types of problems.\n \n Labelling of conflicts markers\n@@ -662,34 +668,33 @@ Labelling of conflicts markers\n \n When there are content conflicts, the merge machinery tries to\n annotate each side's conflict markers with the commits where the\n-content came from.  Since the am backend drops the original\n+content came from.  Since the apply backend drops the original\n information about the rebased commits and their parents (and instead\n generates new fake commits based off limited information in the\n generated patches), those commits cannot be identified; instead it has\n to fall back to a commit summary.  Also, when merge.conflictStyle is\n-set to diff3, the am backend will use \"constructed merge base\" to\n+set to diff3, the apply backend will use \"constructed merge base\" to\n label the content from the merge base, and thus provide no information\n about the merge base commit whatsoever.\n \n-The interactive backend works with the full commits on both sides of\n-history and thus has no such limitations.\n+The merge backend works with the full commits on both sides of history\n+and thus has no such limitations.\n \n Hooks\n ~~~~~\n \n-The am backend has not traditionally called the post-commit hook,\n-while the merge/interactive backend has.  However, this was by\n-accident of implementation rather than by design.  Both backends\n-should have the same behavior, though it is not clear which one is\n-correct.\n+The apply backend has not traditionally called the post-commit hook,\n+while the merge backend has.  However, this was by accident of\n+implementation rather than by design.  Both backends should have the\n+same behavior, though it is not clear which one is correct.\n \n Interruptability\n ~~~~~~~~~~~~~~~~\n \n-The am backend has safety problems with an ill-timed interrupt; if the\n-user presses Ctrl-C at the wrong time to try to abort the rebase, the\n-rebase can enter a state where it cannot be aborted with a subsequent\n-`git rebase --abort`.  The interactive backend does not appear to\n+The apply backend has safety problems with an ill-timed interrupt; if\n+the user presses Ctrl-C at the wrong time to try to abort the rebase,\n+the rebase can enter a state where it cannot be aborted with a\n+subsequent `git rebase --abort`.  The merge backend does not appear to\n suffer from the same shortcoming.  (See\n https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for\n details.)\n@@ -707,9 +712,9 @@ completeness:\n \n * Progress, informational, and error messages: The two backends\n   provide slightly different progress and informational messages.\n-  Also, the am backend writes error messages (such as \"Your files\n-  would be overwritten...\") to stdout, while the interactive backend\n-  writes them to stderr.\n+  Also, the apply backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the merge backend writes\n+  them to stderr.\n \n * State directories: The two backends keep their state in different\n   directories under .git/\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex db8fd699d79..f3036f40c67 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -44,9 +44,8 @@ static GIT_PATH_FUNC(merge_dir, \"rebase-merge\")\n \n enum rebase_type {\n \tREBASE_UNSPECIFIED = -1,\n-\tREBASE_AM,\n+\tREBASE_APPLY,\n \tREBASE_MERGE,\n-\tREBASE_INTERACTIVE,\n \tREBASE_PRESERVE_MERGES\n };\n \n@@ -380,7 +379,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)\n \treturn ret;\n }\n \n-static int run_rebase_interactive(struct rebase_options *opts,\n+static int run_sequencer_rebase(struct rebase_options *opts,\n \t\t\t\t  enum action command)\n {\n \tunsigned flags = 0;\n@@ -462,7 +461,7 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n \t * If we ever want to remap --keep-empty to --empty=keep, insert:\n \t * \topts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n \t */\n-\topts->type = REBASE_INTERACTIVE;\n+\topts->type = REBASE_MERGE;\n \treturn 0;\n }\n \n@@ -555,28 +554,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)\n \t\twarning(_(\"--[no-]rebase-cousins has no effect without \"\n \t\t\t  \"--rebase-merges\"));\n \n-\treturn !!run_rebase_interactive(&opts, command);\n+\treturn !!run_sequencer_rebase(&opts, command);\n }\n \n-static int is_interactive(struct rebase_options *opts)\n+static int is_merge(struct rebase_options *opts)\n {\n-\treturn opts->type == REBASE_INTERACTIVE ||\n+\treturn opts->type == REBASE_MERGE ||\n \t\topts->type == REBASE_PRESERVE_MERGES;\n }\n \n-static void imply_interactive(struct rebase_options *opts, const char *option)\n+static void imply_merge(struct rebase_options *opts, const char *option)\n {\n \tswitch (opts->type) {\n-\tcase REBASE_AM:\n+\tcase REBASE_APPLY:\n \t\tdie(_(\"%s requires an interactive rebase\"), option);\n \t\tbreak;\n-\tcase REBASE_INTERACTIVE:\n+\tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\n \t\tbreak;\n-\tcase REBASE_MERGE:\n-\t\t/* we now implement --merge via --interactive */\n \tdefault:\n-\t\topts->type = REBASE_INTERACTIVE; /* implied */\n+\t\topts->type = REBASE_MERGE; /* implied */\n \t\tbreak;\n \t}\n }\n@@ -785,7 +782,7 @@ static int finish_rebase(struct rebase_options *opts)\n \t * user should see them.\n \t */\n \trun_command_v_opt(argv_gc_auto, RUN_GIT_CMD);\n-\tif (opts->type == REBASE_INTERACTIVE) {\n+\tif (opts->type == REBASE_MERGE) {\n \t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \t\treplay.action = REPLAY_INTERACTIVE_REBASE;\n@@ -1118,8 +1115,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tint status;\n \tconst char *backend, *backend_func;\n \n-\tif (opts->type == REBASE_INTERACTIVE) {\n-\t\t/* Run builtin interactive rebase */\n+\tif (opts->type == REBASE_MERGE) {\n+\t\t/* Run sequencer-based rebase */\n \t\tsetenv(\"GIT_CHERRY_PICK_HELP\", resolvemsg, 1);\n \t\tif (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {\n \t\t\tsetenv(\"GIT_SEQUENCE_EDITOR\", \":\", 1);\n@@ -1132,11 +1129,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \t\t\topts->gpg_sign_opt = tmp;\n \t\t}\n \n-\t\tstatus = run_rebase_interactive(opts, action);\n+\t\tstatus = run_sequencer_rebase(opts, action);\n \t\tgoto finished_rebase;\n \t}\n \n-\tif (opts->type == REBASE_AM) {\n+\tif (opts->type == REBASE_APPLY) {\n \t\tstatus = run_am(opts);\n \t\tgoto finished_rebase;\n \t}\n@@ -1190,7 +1187,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \tadd_var(&script_snippet, \"git_format_patch_opt\",\n \t\topts->git_format_patch_opt.buf);\n \n-\tif (is_interactive(opts) &&\n+\tif (is_merge(opts) &&\n \t    !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {\n \t\tstrbuf_addstr(&script_snippet,\n \t\t\t      \"GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; \");\n@@ -1215,8 +1212,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n finished_rebase:\n \tif (opts->dont_finish_rebase)\n \t\t; /* do nothing */\n-\telse if (opts->type == REBASE_INTERACTIVE)\n-\t\t; /* interactive rebase cleans up after itself */\n+\telse if (opts->type == REBASE_MERGE)\n+\t\t; /* merge backend cleans up after itself */\n \telse if (status == 0) {\n \t\tif (!file_exists(state_dir_path(\"stopped-sha\", opts)))\n \t\t\tfinish_rebase(opts);\n@@ -1348,7 +1345,7 @@ static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n \tBUG_ON_OPT_NEG(unset);\n \tBUG_ON_OPT_ARG(arg);\n \n-\topts->type = REBASE_AM;\n+\topts->type = REBASE_APPLY;\n \n \treturn 0;\n }\n@@ -1361,7 +1358,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n \tBUG_ON_OPT_NEG(unset);\n \tBUG_ON_OPT_ARG(arg);\n \n-\tif (!is_interactive(opts))\n+\tif (!is_merge(opts))\n \t\topts->type = REBASE_MERGE;\n \n \treturn 0;\n@@ -1376,7 +1373,7 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n \tBUG_ON_OPT_NEG(unset);\n \tBUG_ON_OPT_ARG(arg);\n \n-\topts->type = REBASE_INTERACTIVE;\n+\topts->type = REBASE_MERGE;\n \topts->flags |= REBASE_INTERACTIVE_EXPLICIT;\n \n \treturn 0;\n@@ -1440,7 +1437,7 @@ static void set_reflog_action(struct rebase_options *options)\n \tconst char *env;\n \tstruct strbuf buf = STRBUF_INIT;\n \n-\tif (!is_interactive(options))\n+\tif (!is_merge(options))\n \t\treturn;\n \n \tenv = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n@@ -1537,8 +1534,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n-\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n-\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t{ OPTION_CALLBACK, 0, \"apply\", &options, NULL,\n+\t\t\tN_(\"use apply strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n \t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n@@ -1615,7 +1612,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"It looks like 'git am' is in progress. Cannot rebase.\"));\n \n \tif (is_directory(apply_dir())) {\n-\t\toptions.type = REBASE_AM;\n+\t\toptions.type = REBASE_APPLY;\n \t\toptions.state_dir = apply_dir();\n \t} else if (is_directory(merge_dir())) {\n \t\tstrbuf_reset(&buf);\n@@ -1627,7 +1624,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tstrbuf_reset(&buf);\n \t\t\tstrbuf_addf(&buf, \"%s/interactive\", merge_dir());\n \t\t\tif(file_exists(buf.buf)) {\n-\t\t\t\toptions.type = REBASE_INTERACTIVE;\n+\t\t\t\toptions.type = REBASE_MERGE;\n \t\t\t\toptions.flags |= REBASE_INTERACTIVE_EXPLICIT;\n \t\t\t} else\n \t\t\t\toptions.type = REBASE_MERGE;\n@@ -1667,12 +1664,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n \n-\tif (action == ACTION_EDIT_TODO && !is_interactive(&options))\n+\tif (action == ACTION_EDIT_TODO && !is_merge(&options))\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n \tif (trace2_is_enabled()) {\n-\t\tif (is_interactive(&options))\n+\t\tif (is_merge(&options))\n \t\t\ttrace2_cmd_mode(\"interactive\");\n \t\telse if (exec.nr)\n \t\t\ttrace2_cmd_mode(\"interactive-exec\");\n@@ -1748,7 +1745,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \tcase ACTION_QUIT: {\n-\t\tif (options.type == REBASE_INTERACTIVE) {\n+\t\tif (options.type == REBASE_MERGE) {\n \t\t\tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \t\t\treplay.action = REPLAY_INTERACTIVE_REBASE;\n@@ -1831,7 +1828,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\targv_array_push(&options.git_am_opts, \"-q\");\n \n \tif (options.empty != EMPTY_UNSPECIFIED)\n-\t\timply_interactive(&options, \"--empty\");\n+\t\timply_merge(&options, \"--empty\");\n \n \tif (gpg_sign) {\n \t\tfree(options.gpg_sign_opt);\n@@ -1841,7 +1838,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (exec.nr) {\n \t\tint i;\n \n-\t\timply_interactive(&options, \"--exec\");\n+\t\timply_merge(&options, \"--exec\");\n \n \t\tstrbuf_reset(&buf);\n \t\tfor (i = 0; i < exec.nr; i++)\n@@ -1857,7 +1854,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\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-\t\timply_interactive(&options, \"--rebase-merges\");\n+\t\timply_merge(&options, \"--rebase-merges\");\n \t}\n \n \tif (strategy_options.nr) {\n@@ -1876,10 +1873,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (options.strategy) {\n \t\toptions.strategy = xstrdup(options.strategy);\n \t\tswitch (options.type) {\n-\t\tcase REBASE_AM:\n+\t\tcase REBASE_APPLY:\n \t\t\tdie(_(\"--strategy requires --merge or --interactive\"));\n \t\tcase REBASE_MERGE:\n-\t\tcase REBASE_INTERACTIVE:\n \t\tcase REBASE_PRESERVE_MERGES:\n \t\t\t/* compatible */\n \t\t\tbreak;\n@@ -1892,34 +1888,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (options.type == REBASE_MERGE)\n-\t\timply_interactive(&options, \"--merge\");\n+\t\timply_merge(&options, \"--merge\");\n \n \tif (options.root && !options.onto_name)\n-\t\timply_interactive(&options, \"--root without --onto\");\n+\t\timply_merge(&options, \"--root without --onto\");\n \n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n-\t\t/* all am options except -q are compatible only with --am */\n+\tif (options.git_am_opts.argc || options.type == REBASE_APPLY) {\n+\t\t/* all am options except -q are compatible only with --apply */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n \t\t\t\tbreak;\n \n \t\tif (i >= 0) {\n-\t\t\tif (is_interactive(&options))\n-\t\t\t\tdie(_(\"cannot combine am options with either \"\n-\t\t\t\t      \"interactive or merge options\"));\n+\t\t\tif (is_merge(&options))\n+\t\t\t\tdie(_(\"cannot combine apply options with \"\n+\t\t\t\t      \"merge options\"));\n \t\t\telse\n-\t\t\t\toptions.type = REBASE_AM;\n+\t\t\t\toptions.type = REBASE_APPLY;\n \t\t}\n \t}\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n \t\tif (!strcmp(options.default_backend, \"merge\"))\n-\t\t\timply_interactive(&options, \"--merge\");\n-\t\telse if (!strcmp(options.default_backend, \"am\"))\n-\t\t\toptions.type = REBASE_AM;\n+\t\t\timply_merge(&options, \"--merge\");\n+\t\telse if (!strcmp(options.default_backend, \"apply\"))\n+\t\t\toptions.type = REBASE_APPLY;\n \t\telse\n \t\t\tdie(_(\"Unknown rebase backend: %s\"),\n \t\t\t    options.default_backend);\n@@ -1927,11 +1923,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n-\tcase REBASE_INTERACTIVE:\n \tcase REBASE_PRESERVE_MERGES:\n \t\toptions.state_dir = merge_dir();\n \t\tbreak;\n-\tcase REBASE_AM:\n+\tcase REBASE_APPLY:\n \t\toptions.state_dir = apply_dir();\n \t\tbreak;\n \tdefault:\n@@ -1946,7 +1941,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\telse\n \t\t\toptions.empty = EMPTY_DROP;\n \t}\n-\tif (reschedule_failed_exec > 0 && !is_interactive(&options))\n+\tif (reschedule_failed_exec > 0 && !is_merge(&options))\n \t\tdie(_(\"--reschedule-failed-exec requires \"\n \t\t      \"--exec or --interactive\"));\n \tif (reschedule_failed_exec >= 0)\n@@ -2247,7 +2242,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdiff_flush(&opts);\n \t}\n \n-\tif (is_interactive(&options))\n+\tif (is_merge(&options))\n \t\tgoto run_rebase;\n \n \t/* Detach HEAD and reset the tree */\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 0a491f23632..7c7e085043d 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '\n+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--apply)' '\n \tgit checkout -B default-base master &&\n \tgit checkout -B default topic &&\n \tgit config branch.default.remote . &&\n \tgit config branch.default.merge refs/heads/default-base &&\n-\tgit rebase --am &&\n+\tgit rebase --apply &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual &&\n \tgit checkout default-base &&\n \tgit reset --hard HEAD^ &&\n \tgit checkout default &&\n-\tgit rebase --am &&\n+\tgit rebase --apply &&\n \tgit rev-parse --verify default-base >expect &&\n \tgit rev-parse default~1 >actual &&\n \ttest_cmp expect actual\n@@ -224,9 +224,9 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase --am -q is quiet' '\n+test_expect_success 'rebase --apply -q is quiet' '\n \tgit checkout -b quiet topic &&\n-\tgit rebase --am -q master >output.out 2>&1 &&\n+\tgit rebase --apply -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n@@ -315,7 +315,7 @@ EOF\n \ttest_cmp From_.msg out\n '\n \n-test_expect_success 'rebase --am and --show-current-patch' '\n+test_expect_success 'rebase --apply and --show-current-patch' '\n \ttest_create_repo conflict-apply &&\n \t(\n \t\tcd conflict-apply &&\n@@ -325,13 +325,13 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --am -f --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase --apply -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\n '\n \n-test_expect_success 'rebase --am and .gitattributes' '\n+test_expect_success 'rebase --apply and .gitattributes' '\n \ttest_create_repo attributes &&\n \t(\n \t\tcd attributes &&\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex 50803958fd0..f18bae94507 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '\n \t)\n '\n \n-test_expect_failure 'rebase --am: directory rename detected' '\n+test_expect_failure 'rebase --apply: directory rename detected' '\n \t(\n \t\tcd dir-rename &&\n \n \t\tgit checkout B^0 &&\n \n-\t\tgit -c merge.directoryRenames=true rebase --am A &&\n+\t\tgit -c merge.directoryRenames=true rebase --apply A &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 5 out &&\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f964b2cd41d..2f51606e777 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit checkout conflict-branch &&\n \t(\n \t\tset_fake_editor &&\n-\t\ttest_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&\n+\t\ttest_must_fail git rebase -f --apply --onto HEAD~2 HEAD~ &&\n \t\ttest_must_fail git rebase --edit-todo\n \t) &&\n \tgit rebase --abort\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex 7ce617fc1fb..61b76f33019 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '\n '\n \n test_expect_success 'rebase against master twice' '\n-\tgit rebase --am master >out &&\n+\tgit rebase --apply master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase against master twice with --force' '\n-\tgit rebase --force-rebase --am master >out &&\n+\tgit rebase --force-rebase --apply master >out &&\n \ttest_i18ngrep \"Current branch topic is up to date, rebase forced\" out\n '\n \n test_expect_success 'rebase against master twice from another branch' '\n \tgit checkout topic^ &&\n-\tgit rebase --am master topic >out &&\n+\tgit rebase --apply master topic >out &&\n \ttest_i18ngrep \"Current branch topic is up to date\" out\n '\n \n test_expect_success 'rebase fast-forward to master' '\n \tgit checkout topic^ &&\n-\tgit rebase --am topic >out &&\n+\tgit rebase --apply topic >out &&\n \ttest_i18ngrep \"Fast-forwarded HEAD to topic\" out\n '\n \n@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \tgit checkout -b reflog-topic start &&\n \ttest_commit reflog-to-rebase &&\n \n-\tgit rebase --am reflog-onto &&\n+\tgit rebase --apply reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-topic\n@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '\n \ttest_cmp expect actual &&\n \n \tgit checkout -b reflog-prefix reflog-to-rebase &&\n-\tGIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&\n+\tGIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto &&\n \tgit log -g --format=%gs -3 >actual &&\n \tcat >expect <<-\\EOF &&\n \trebase finished: returning to refs/heads/reflog-prefix\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 3e318261703..97efea0f569 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -96,14 +96,14 @@ testrebase() {\n \t'\n }\n \n-testrebase \" --am\" .git/rebase-apply\n+testrebase \" --apply\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n \n-test_expect_success 'rebase --am --quit' '\n+test_expect_success 'rebase --apply --quit' '\n \tcd \"$work_dir\" &&\n \t# Clean up the state from the previous one\n \tgit reset --hard pre-rebase &&\n-\ttest_must_fail git rebase --am master &&\n+\ttest_must_fail git rebase --apply master &&\n \ttest_path_is_dir .git/rebase-apply &&\n \thead_before=$(git rev-parse HEAD) &&\n \tgit rebase --quit &&\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 3816159e207..b97ea623639 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -34,7 +34,7 @@ test_expect_success setup '\n \tremove_progress_re=\"$(printf \"s/.*\\\\r//\")\"\n '\n \n-create_expected_success_am () {\n+create_expected_success_apply () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n \tFirst, rewinding head to replay your work on top of it...\n@@ -44,7 +44,7 @@ create_expected_success_am () {\n \tEOF\n }\n \n-create_expected_success_interactive () {\n+create_expected_success_merge () {\n \tq_to_cr >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n \tApplied autostash.\n@@ -52,7 +52,7 @@ create_expected_success_interactive () {\n \tEOF\n }\n \n-create_expected_failure_am () {\n+create_expected_failure_apply () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n \tFirst, rewinding head to replay your work on top of it...\n@@ -64,7 +64,7 @@ create_expected_failure_am () {\n \tEOF\n }\n \n-create_expected_failure_interactive () {\n+create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n \tApplying autostash resulted in conflicts.\n@@ -101,9 +101,9 @@ testrebase () {\n \n \ttest_expect_success \"rebase$type --autostash: check output\" '\n \t\ttest_when_finished git branch -D rebased-feature-branch &&\n-\t\tsuffix=${type#\\ --} && suffix=${suffix:-am} &&\n-\t\tif test ${suffix} = \"merge\"; then\n-\t\t\tsuffix=interactive\n+\t\tsuffix=${type#\\ --} && suffix=${suffix:-apply} &&\n+\t\tif test ${suffix} = \"interactive\"; then\n+\t\t\tsuffix=merge\n \t\tfi &&\n \t\tcreate_expected_success_$suffix &&\n \t\tsed \"$remove_progress_re\" <actual >actual2 &&\n@@ -202,9 +202,9 @@ testrebase () {\n \n \ttest_expect_success \"rebase$type: check output with conflicting stash\" '\n \t\ttest_when_finished git branch -D rebased-feature-branch &&\n-\t\tsuffix=${type#\\ --} && suffix=${suffix:-am} &&\n-\t\tif test ${suffix} = \"merge\"; then\n-\t\t\tsuffix=interactive\n+\t\tsuffix=${type#\\ --} && suffix=${suffix:-apply} &&\n+\t\tif test ${suffix} = \"interactive\"; then\n+\t\t\tsuffix=merge\n \t\tfi &&\n \t\tcreate_expected_failure_$suffix &&\n \t\tsed \"$remove_progress_re\" <actual >actual2 &&\n@@ -234,7 +234,7 @@ test_expect_success \"rebase: noop rebase\" '\n \tgit checkout feature-branch\n '\n \n-testrebase \" --am\" .git/rebase-apply\n+testrebase \" --apply\" .git/rebase-apply\n testrebase \" --merge\" .git/rebase-merge\n testrebase \" --interactive\" .git/rebase-merge\n \ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex e12304012a2..cf8dfd6c203 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -26,7 +26,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' c..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -50,7 +50,7 @@ test_run_rebase () {\n \t\ttest_cmp_rev e HEAD\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -66,7 +66,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' b..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -83,7 +83,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd e' branch-b..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -98,7 +98,7 @@ test_run_rebase () {\n \t\ttest_cmp_rev e HEAD\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n@@ -139,7 +139,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd i' h..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -154,7 +154,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd' h..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -169,7 +169,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd i' f..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -184,7 +184,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd gp i' h..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -212,7 +212,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'j d k l' c..\n \t\"\n }\n-test_run_rebase failure --am\n+test_run_rebase failure --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -227,7 +227,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd k l' c..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -242,7 +242,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'd k l' j..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -282,7 +282,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' c..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -297,7 +297,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' c..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -312,7 +312,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' m..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n@@ -328,7 +328,7 @@ test_run_rebase () {\n \t\"\n }\n \n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n@@ -343,7 +343,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'x y' m..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nindex cfb1ebc1ff9..98fc2a558a0 100755\n--- a/t/t3424-rebase-empty.sh\n+++ b/t/t3424-rebase-empty.sh\n@@ -34,11 +34,11 @@ test_expect_success 'setup test repository' '\n \tgit commit -m \"Five letters ought to be enough for anybody\"\n '\n \n-test_expect_failure 'rebase (am-backend)' '\n+test_expect_failure 'rebase (apply-backend)' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tgit checkout -B testing localmods &&\n-\t# rebase (--am) should not drop commits that start empty\n-\tgit rebase upstream &&\n+\t# rebase (--apply) should not drop commits that start empty\n+\tgit rebase --apply upstream &&\n \n \ttest_write_lines D C B A >expect &&\n \tgit log --format=%s >actual &&\ndiff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\nindex 19700b025b5..e42faa44e74 100755\n--- a/t/t3425-rebase-topology-merges.sh\n+++ b/t/t3425-rebase-topology-merges.sh\n@@ -54,7 +54,7 @@ test_run_rebase () {\n \t\ttest_linear_range 'n o' e..\n \t\"\n }\n-test_run_rebase success --am\n+test_run_rebase success --apply\n test_run_rebase success -m\n test_run_rebase success -i\n \n@@ -70,7 +70,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" d..\n \t\"\n }\n-test_run_rebase success 'n o e' --am\n+test_run_rebase success 'n o e' --apply\n test_run_rebase success 'n o e' -m\n test_run_rebase success 'n o e' -i\n \n@@ -86,7 +86,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' --am\n+test_run_rebase success 'd n o e' --apply\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \n@@ -102,7 +102,7 @@ test_run_rebase () {\n \t\ttest_linear_range \"\\'\"$expected\"\\'\" c..\n \t\"\n }\n-test_run_rebase success 'd n o e' --am\n+test_run_rebase success 'd n o e' --apply\n test_run_rebase success 'd n o e' -m\n test_run_rebase success 'd n o e' -i\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 4b3cecce560..6c9d4a13758 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -28,8 +28,8 @@ test_rebase_same_head () {\n \tshift &&\n \tcmp_f=\"$1\" &&\n \tshift &&\n-\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --am\" \"$*\" &&\n-\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --am --no-ff\" \"$*\"\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --apply\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --apply --no-ff\" \"$*\"\n \ttest_rebase_same_head_ $status_n $what_n $cmp_n \" --merge\" \"$*\" &&\n \ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --merge --no-ff\" \"$*\"\n }\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex a8a73616e46..80750a817e9 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n-test_expect_success 'git rebase --am' '\n+test_expect_success 'git rebase --apply' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --am --onto A B &&\n+\ttest_must_fail git rebase --apply --onto A B &&\n \techo C > foo &&\n \tgit add foo &&\n \tgit rebase --continue &&\n@@ -68,10 +68,10 @@ test_expect_success 'git rebase --am' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --am --skip' '\n+test_expect_success 'git rebase --apply --skip' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --am --onto A B &&\n+\ttest_must_fail git rebase --apply --onto A B &&\n \ttest_must_fail git rebase --skip &&\n \techo D > foo &&\n \tgit add foo &&\n@@ -84,10 +84,10 @@ test_expect_success 'git rebase --am --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --am --skip the last one' '\n+test_expect_success 'git rebase --apply --skip the last one' '\n \tgit reset --hard F &&\n \tclear_hook_input &&\n-\ttest_must_fail git rebase --am --onto D A &&\n+\ttest_must_fail git rebase --apply --onto D A &&\n \tgit rebase --skip &&\n \techo rebase >expected.args &&\n \tcat >expected.data <<-EOF &&\n@@ -128,7 +128,7 @@ test_expect_success 'git rebase -m --skip' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase with implicit use of interactive backend' '\n+test_expect_success 'git rebase with implicit use of merge backend' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n \ttest_must_fail git rebase --keep-empty --onto A B &&\n@@ -143,7 +143,7 @@ test_expect_success 'git rebase with implicit use of interactive backend' '\n \tverify_hook_input\n '\n \n-test_expect_success 'git rebase --skip with implicit use of interactive backend' '\n+test_expect_success 'git rebase --skip with implicit use of merge backend' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n \ttest_must_fail git rebase --keep-empty --onto A B &&\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 4f9e7f7ff6b..2f86fca0428 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -297,7 +297,7 @@ test_expect_success '--rebase (merge) fast forward' '\n test_expect_success '--rebase (am) fast forward' '\n \tgit reset --hard before-rebase &&\n \n-\tgit -c rebase.backend=am pull --rebase . ff &&\n+\tgit -c rebase.backend=apply pull --rebase . ff &&\n \ttest_cmp_rev HEAD ff &&\n \n \t# The above only validates the result.  Did we actually bypass rebase?\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex d383ce81302..f4655bb358f 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -199,11 +199,11 @@ test_expect_success 'rebase --merge describes parent of commit being picked' '\n \t)\n '\n \n-test_expect_success 'rebase --am describes fake ancestor base' '\n+test_expect_success 'rebase --apply describes fake ancestor base' '\n \t(\n \t\tcd rebase &&\n \t\tgit rebase --abort &&\n-\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase --apply master &&\n \t\tgrep \"||||||| constructed merge base\" file\n \t)\n '\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex d22b0acf2ac..29518e0949b 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '\n '\n \n \n-test_expect_success 'status when rebase --am in progress before resolving conflicts' '\n+test_expect_success 'status when rebase --apply in progress before resolving conflicts' '\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''rebase_conflicts'\\'' on '\\''$ONTO'\\''.\n@@ -94,11 +94,11 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase --am in progress before rebase --continue' '\n+test_expect_success 'status when rebase --apply in progress before rebase --continue' '\n \tgit reset --hard rebase_conflicts &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&\n \techo three >main.txt &&\n \tgit add main.txt &&\n \tcat >expected <<EOF &&\n@@ -688,7 +688,7 @@ EOF\n '\n \n \n-test_expect_success 'status when rebase --am conflicts with statushints disabled' '\n+test_expect_success 'status when rebase --apply conflicts with statushints disabled' '\n \tgit reset --hard master &&\n \tgit checkout -b statushints_disabled &&\n \ttest_when_finished \"git config --local advice.statushints true\" &&\n@@ -698,7 +698,7 @@ test_expect_success 'status when rebase --am conflicts with statushints disabled\n \ttest_commit three_statushints main.txt three &&\n \ttest_when_finished \"git rebase --abort\" &&\n \tONTO=$(git rev-parse --short HEAD^^) &&\n-\ttest_must_fail git rebase --am HEAD^ --onto HEAD^^ &&\n+\ttest_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&\n \tcat >expected <<EOF &&\n rebase in progress; onto $ONTO\n You are currently rebasing branch '\\''statushints_disabled'\\'' on '\\''$ONTO'\\''.\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 7ca35d358d6..ab5da2cabc4 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -193,7 +193,7 @@ test_expect_success 'prompt - rebase am' '\n \tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase --am b1 b2 &&\n+\ttest_must_fail git rebase --apply b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n"},{"id":"391852","messageId":"3dccb58d0660de8ab931e1540f5fd0c0083954e9.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 07/20] rebase: make sure to pass along the quiet flag to the sequencer","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:28Z","receivedAt":"2020-02-15T21:37:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c  | 3 ++-\n t/t3400-rebase.sh | 8 +++++++-\n 2 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7551f950593..1a664137d29 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \treplay.allow_empty_message = opts->allow_empty_message;\n \treplay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n \treplay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n+\treplay.quiet = !(opts->flags & REBASE_NO_QUIET);\n \treplay.verbose = opts->flags & REBASE_VERBOSE;\n \treplay.reschedule_failed_exec = opts->reschedule_failed_exec;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n \t\t\t   N_(\"be quiet. implies --no-stat\"),\n-\t\t\t   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),\n+\t\t\t   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\n \t\tOPT_BIT('v', \"verbose\", &options.flags,\n \t\t\tN_(\"display a diffstat of what changed upstream\"),\n \t\t\tREBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 221b35f2df3..79762b989a4 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '\n \ttest_cmp expect D\n '\n \n-test_expect_success 'rebase -q is quiet' '\n+test_expect_success 'rebase --am -q is quiet' '\n \tgit checkout -b quiet topic &&\n \tgit rebase -q master >output.out 2>&1 &&\n \ttest_must_be_empty output.out\n '\n \n+test_expect_success 'rebase --merge -q is quiet' '\n+\tgit checkout -B quiet topic &&\n+\tgit rebase --merge -q master >output.out 2>&1 &&\n+\ttest_must_be_empty output.out\n+'\n+\n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n \t\techo \"One\" &&\n-- \ngitgitgadget\n\n"},{"id":"391854","messageId":"f0f00c1e7fdecaa159848b6c51dea3d7180e1283.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 15/20] rebase: drop '-i' from the reflog for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:36Z","receivedAt":"2020-02-15T21:37:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nA large variety of rebase types are supported by the interactive\nmachinery, not just the explicitly interactive ones.  These all share\nthe same code and write the same reflog messages, but the \"-i\" moniker\nin those messages doesn't really have much meaning.  It also becomes\nsomewhat distracting once we switch the default from the am-backend to\nthe interactive one.  Just remove the \"-i\" from these messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/rebase.c              |  2 +-\n sequencer.c                   |  8 ++++----\n t/t3404-rebase-interactive.sh | 10 +++++-----\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77abe674cb9..ffaa7935240 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)\n \tif (env && strcmp(\"rebase\", env))\n \t\treturn; /* only override it if it is \"rebase\" */\n \n-\tstrbuf_addf(&buf, \"rebase -i (%s)\", options->action);\n+\tstrbuf_addf(&buf, \"rebase (%s)\", options->action);\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);\n \tstrbuf_release(&buf);\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex f475d2a3b1c..f8e242b6695 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)\n \t\t\tchar *eol = strchr(p, '\\n');\n \t\t\tif (eol)\n \t\t\t\t*eol = '\\0';\n-\t\t\tif (delete_ref(\"(rebase -i) cleanup\", p, NULL, 0) < 0) {\n+\t\t\tif (delete_ref(\"(rebase) cleanup\", p, NULL, 0) < 0) {\n \t\t\t\twarning(_(\"could not delete '%s'\"), p);\n \t\t\t\tret = -1;\n \t\t\t}\n@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)\n \tcase REPLAY_PICK:\n \t\treturn N_(\"cherry-pick\");\n \tcase REPLAY_INTERACTIVE_REBASE:\n-\t\treturn N_(\"rebase -i\");\n+\t\treturn N_(\"rebase\");\n \t}\n \tdie(_(\"unknown action: %d\"), opts->action);\n }\n@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t/*\n \t\t * TRANSLATORS: %s will be \"revert\", \"cherry-pick\" or\n-\t\t * \"rebase -i\".\n+\t\t * \"rebase\".\n \t\t */\n \t\treturn error(_(\"%s: Unable to write new index file\"),\n \t\t\t_(action_name(opts)));\n@@ -3199,7 +3199,7 @@ static int do_label(struct repository *r, const char *name, int len)\n \t\treturn error(_(\"illegal label name: '%.*s'\"), len, name);\n \n \tstrbuf_addf(&ref_name, \"refs/rewritten/%.*s\", len, name);\n-\tstrbuf_addf(&msg, \"rebase -i (label) '%.*s'\", len, name);\n+\tstrbuf_addf(&msg, \"rebase (label) '%.*s'\", len, name);\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction) {\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex c41531f3490..a31583eb2fd 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' '\n '\n \n test_expect_success 'reflog for the branch shows correct finish message' '\n-\tprintf \"rebase -i (finish): refs/heads/branch1 onto %s\\n\" \\\n+\tprintf \"rebase (finish): refs/heads/branch1 onto %s\\n\" \\\n \t\t\"$(git rev-parse branch2)\" >expected &&\n \tgit log -g --pretty=%gs -1 refs/heads/branch1 >actual &&\n \ttest_cmp expected actual\n@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' '\n \tgit branch -f branch-reflog-test H &&\n \tgit rebase -i --onto I F branch-reflog-test &&\n \tcat >expect <<-\\EOF &&\n-\trebase -i (finish): returning to refs/heads/branch-reflog-test\n-\trebase -i (pick): H\n-\trebase -i (pick): G\n-\trebase -i (start): checkout I\n+\trebase (finish): returning to refs/heads/branch-reflog-test\n+\trebase (pick): H\n+\trebase (pick): G\n+\trebase (start): checkout I\n \tEOF\n \tgit reflog -n4 HEAD |\n \tsed \"s/[^:]*: //\" >actual &&\n-- \ngitgitgadget\n\n"},{"id":"391851","messageId":"e4c04d3cf8b0c6a459bf192bfb34aca9733870c5.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 14/20] git-prompt: change the prompt for interactive-based rebases","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:35Z","receivedAt":"2020-02-15T21:37:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn the past, we had different prompts for different types of rebases:\n   REBASE: for am-based rebases\n   REBASE-m: for merge-based rebases\n   REBASE-i: for interactive-based rebases\n\nIt's not clear why this distinction was necessary or helpful; when the\nprompt was added in commit e75201963f67 (\"Improve bash prompt to detect\nvarious states like an unfinished merge\", 2007-09-30), it simply added\nthese three different types.  Perhaps there was a useful purpose back\nthen, but there have been some changes:\n\n  * The merge backend was deleted after being implemented on top of the\n    interactive backend, causing the prompt for merge-based rebases to\n    change from REBASE-m to REBASE-i.\n  * The interactive backend is used for multiple different types of\n    non-interactive rebases, so the \"-i\" part of the prompt doesn't\n    really mean what it used to.\n  * Rebase backends have gained more abilities and have a great deal of\n    overlap, sometimes making it hard to distinguish them.\n  * Behavioral differences between the backends have also been ironed\n    out.\n  * We want to change the default backend from am to interactive, which\n    means people would get \"REBASE-i\" by default if we didn't change\n    the prompt, and only if they specified --am or --whitespace or -C\n    would they get the \"REBASE\" prompt.\n  * In the future, we plan to have \"--whitespace\", \"-C\", and even \"--am\"\n    run the interactive backend once it can handle everything the\n    am-backend can.\n\nFor all these reasons, make the prompt for any type of rebase just be\n\"REBASE\".\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n contrib/completion/git-prompt.sh | 6 +-----\n t/t9903-bash-prompt.sh           | 8 ++++----\n 2 files changed, 5 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh\nindex 1d510cd47be..014cd7c3cfc 100644\n--- a/contrib/completion/git-prompt.sh\n+++ b/contrib/completion/git-prompt.sh\n@@ -429,11 +429,7 @@ __git_ps1 ()\n \t\t__git_eread \"$g/rebase-merge/head-name\" b\n \t\t__git_eread \"$g/rebase-merge/msgnum\" step\n \t\t__git_eread \"$g/rebase-merge/end\" total\n-\t\tif [ -f \"$g/rebase-merge/interactive\" ]; then\n-\t\t\tr=\"|REBASE-i\"\n-\t\telse\n-\t\t\tr=\"|REBASE-m\"\n-\t\tfi\n+\t\tr=\"|REBASE\"\n \telse\n \t\tif [ -d \"$g/rebase-apply\" ]; then\n \t\t\t__git_eread \"$g/rebase-apply/next\" step\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex 88bc733ad69..7ca35d358d6 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '\n '\n \n test_expect_success 'prompt - interactive rebase' '\n-\tprintf \" (b1|REBASE-i 2/3)\" >expected &&\n+\tprintf \" (b1|REBASE 2/3)\" >expected &&\n \twrite_script fake_editor.sh <<-\\EOF &&\n \t\techo \"exec echo\" >\"$1\"\n \t\techo \"edit $(git log -1 --format=\"%h\")\" >>\"$1\"\n@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '\n '\n \n test_expect_success 'prompt - rebase merge' '\n-\tprintf \" (b2|REBASE-i 1/3)\" >expected &&\n+\tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n \ttest_must_fail git rebase --merge b1 b2 &&\n@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '\n \ttest_cmp expected \"$actual\"\n '\n \n-test_expect_success 'prompt - rebase' '\n+test_expect_success 'prompt - rebase am' '\n \tprintf \" (b2|REBASE 1/3)\" >expected &&\n \tgit checkout b2 &&\n \ttest_when_finished \"git checkout master\" &&\n-\ttest_must_fail git rebase b1 b2 &&\n+\ttest_must_fail git rebase --am b1 b2 &&\n \ttest_when_finished \"git rebase --abort\" &&\n \t__git_ps1 >\"$actual\" &&\n \ttest_cmp expected \"$actual\"\n-- \ngitgitgadget\n\n"},{"id":"391853","messageId":"de1b0aaca88674d95083a22c09be44c5cdaea663.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 13/20] rebase: add an --am option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:34Z","receivedAt":"2020-02-15T21:37:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCurrently, this option doesn't do anything except error out if any\noptions requiring the interactive-backend are also passed.  However,\nwhen we make the default backend configurable later in this series, this\nflag will provide a way to override the config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 11 ++++++++++-\n builtin/rebase.c             | 18 +++++++++++++++++-\n 2 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6e2569cd8a7..93092da3bf6 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.\n \toriginal branch. The index and working tree are also left\n \tunchanged as a result.\n \n+--am:\n+\tUse git-am internally to rebase.  This option may become a\n+\tno-op in the future once the interactive backend handles\n+\teverything the am one does.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n --empty={drop,keep,ask}::\n \tHow to handle commits that are not empty to start and are not\n \tclean cherry-picks of any upstream commit, but which become\n@@ -378,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below.\n \tEnsure at least <n> lines of surrounding context match before\n \tand after each change.  When fewer lines of surrounding\n \tcontext exist they all must match.  By default no context is\n-\tever ignored.\n+\tever ignored.  Implies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -418,6 +425,7 @@ with `--keep-base` in order to drop those commits from your branch.\n --whitespace=<option>::\n \tThese flags are passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n+\tImplies --am.\n +\n See also INCOMPATIBLE OPTIONS below.\n \n@@ -561,6 +569,7 @@ INCOMPATIBLE OPTIONS\n \n The following options:\n \n+ * --am\n  * --committer-date-is-author-date\n  * --ignore-date\n  * --ignore-whitespace\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex a2f05f783d8..77abe674cb9 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \treturn res && is_linear_history(onto, head);\n }\n \n+static int parse_opt_am(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct rebase_options *opts = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\tBUG_ON_OPT_ARG(arg);\n+\n+\topts->type = REBASE_AM;\n+\n+\treturn 0;\n+}\n+\n /* -i followed by -m is still -i */\n static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n {\n@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_CMDMODE(0, \"show-current-patch\", &action,\n \t\t\t    N_(\"show the patch file being applied or merged\"),\n \t\t\t    ACTION_SHOW_CURRENT_PATCH),\n+\t\t{ OPTION_CALLBACK, 0, \"am\", &options, NULL,\n+\t\t\tN_(\"use apply-mail strategies to rebase\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tparse_opt_am },\n \t\t{ OPTION_CALLBACK, 'm', \"merge\", &options, NULL,\n \t\t\tN_(\"use merging strategies to rebase\"),\n \t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \n-\tif (options.git_am_opts.argc) {\n+\tif (options.git_am_opts.argc || options.type == REBASE_AM) {\n \t\t/* all am options except -q are compatible only with --am */\n \t\tfor (i = options.git_am_opts.argc - 1; i >= 0; i--)\n \t\t\tif (strcmp(options.git_am_opts.argv[i], \"-q\"))\n-- \ngitgitgadget\n\n"},{"id":"391855","messageId":"11e96b9a5fab2fe18a9f97d66358febdd810d99c.1581802602.git.gitgitgadget@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"[PATCH v5 11/20] git-rebase.txt: add more details about behavioral differences of backends","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-15T21:36:32Z","receivedAt":"2020-02-15T21:37:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++---\n 1 file changed, 80 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex e1c6f918013..6e2569cd8a7 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -416,7 +416,7 @@ with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n-\tThese flag are passed to the 'git apply' program\n+\tThese flags are passed to the 'git apply' program\n \t(see linkgit:git-apply[1]) that applies the patch.\n +\n See also INCOMPATIBLE OPTIONS below.\n@@ -563,8 +563,8 @@ The following options:\n \n  * --committer-date-is-author-date\n  * --ignore-date\n- * --whitespace\n  * --ignore-whitespace\n+ * --whitespace\n  * -C\n \n are incompatible with the following options:\n@@ -615,9 +615,84 @@ handling commits that become empty.\n Directory rename detection\n ~~~~~~~~~~~~~~~~~~~~~~~~~~\n \n-Directory rename heuristics are enabled in the merge and interactive\n-backends.  Due to the lack of accurate tree information, directory\n-rename detection is disabled in the am backend.\n+Due to the lack of accurate tree information (arising from\n+constructing fake ancestors with the limited information available in\n+patches), directory rename detection is disabled in the am backend.\n+Disabled directory rename detection means that if one side of history\n+renames a directory and the other adds new files to the old directory,\n+then the new files will be left behind in the old directory without\n+any warning at the time of rebasing that you may want to move these\n+files into the new directory.\n+\n+Directory rename detection works with the merge and interactive\n+backends to provide you warnings in such cases.\n+\n+Context\n+~~~~~~~\n+\n+The am backend works by creating a sequence of patches (by calling\n+`format-patch` internally), and then applying the patches in sequence\n+(calling `am` internally).  Patches are composed of multiple hunks,\n+each with line numbers, a context region, and the actual changes.  The\n+line numbers have to be taken with some fuzz, since the other side\n+will likely have inserted or deleted lines earlier in the file.  The\n+context region is meant to help find how to adjust the line numbers in\n+order to apply the changes to the right lines.  However, if multiple\n+areas of the code have the same surrounding lines of context, the\n+wrong one can be picked.  There are real-world cases where this has\n+caused commits to be reapplied incorrectly with no conflicts reported.\n+Setting diff.context to a larger value may prevent such types of\n+problems, but increases the chance of spurious conflicts (since it\n+will require more lines of matching context to apply).\n+\n+The interactive backend works with a full copy of each relevant file,\n+insulating it from these types of problems.\n+\n+Labelling of conflicts markers\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+When there are content conflicts, the merge machinery tries to\n+annotate each side's conflict markers with the commits where the\n+content came from.  Since the am backend drops the original\n+information about the rebased commits and their parents (and instead\n+generates new fake commits based off limited information in the\n+generated patches), those commits cannot be identified; instead it has\n+to fall back to a commit summary.  Also, when merge.conflictStyle is\n+set to diff3, the am backend will use \"constructed merge base\" to\n+label the content from the merge base, and thus provide no information\n+about the merge base commit whatsoever.\n+\n+The interactive backend works with the full commits on both sides of\n+history and thus has no such limitations.\n+\n+Hooks\n+~~~~~\n+\n+The am backend has not traditionally called the post-commit hook,\n+while the merge/interactive backend has.  However, this was by\n+accident of implementation rather than by design.  Both backends\n+should have the same behavior, though it is not clear which one is\n+correct.\n+\n+Miscellaneous differences\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+There are a few more behavioral differences that most folks would\n+probably consider inconsequential but which are mentioned for\n+completeness:\n+\n+* Reflog: The two backends will use different wording when describing\n+  the changes made in the reflog, though both will make use of the\n+  word \"rebase\".\n+\n+* Progress, informational, and error messages: The two backends\n+  provide slightly different progress and informational messages.\n+  Also, the am backend writes error messages (such as \"Your files\n+  would be overwritten...\") to stdout, while the interactive backend\n+  writes them to stderr.\n+\n+* State directories: The two backends keep their state in different\n+  directories under .git/\n \n include::merge-strategies.txt[]\n \n-- \ngitgitgadget\n\n"},{"id":"391871","messageId":"f93b9f73-10c5-b94b-df4e-bfdb05a9723a@gmail.com","threadId":"52496","inReplyTo":"CABPp-BGRGkjBk9aY8DUXJY52e5u_aAVd8-dspEcW6Se3v-G4-Q@mail.gmail.com","subject":"Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-02-16T14:46:41Z","receivedAt":"2020-02-16T14:46:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 13/02/2020 18:54, Elijah Newren wrote:\n> On Mon, Feb 10, 2020 at 6:27 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>>\n>> Hi Elijah\n>>\n>> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:\n>>> From: Elijah Newren <newren@gmail.com>\n>>>\n>>> As established in the previous commit and commit b00bf1c9a8dd\n>>> (git-rebase: make --allow-empty-message the default, 2018-06-27), the\n>>> behavior for rebase with different backends in various edge or corner\n>>> cases is often more happenstance than design.  This commit addresses\n>>> another such corner case: commits which \"become empty\".\n>>>\n>>> A careful reader may note that there are two types of commits which would\n>>> become empty due to a rebase:\n>>>\n>>>     * [clean cherry-pick] Commits which are clean cherry-picks of upstream\n>>>       commits, as determined by `git log --cherry-mark ...`.  Re-applying\n>>>       these commits would result in an empty set of changes and a\n>>>       duplicative commit message; i.e. these are commits that have\n>>>       \"already been applied\" upstream.\n>>>\n>>>     * [become empty] Commits which are not empty to start, are not clean\n>>>       cherry-picks of upstream commits, but which still become empty after\n>>>       being rebased.  This happens e.g. when a commit has changes which\n>>>       are a strict subset of the changes in an upstream commit, or when\n>>>       the changes of a commit can be found spread across or among several\n>>>       upstream commits.\n>>>\n>>> Clearly, in both cases the changes in the commit in question are found\n>>> upstream already, but the commit message may not be in the latter case.\n>>>\n>>> When cherry-mark can determine a commit is already upstream, then\n>>> because of how cherry-mark works this means the upstream commit message\n>>> was about the *exact* same set of changes.  Thus, the commit messages\n>>> can be assumed to be fully interchangeable (and are in fact likely to be\n>>> completely identical).  As such, the clean cherry-pick case represents a\n>>> case when there is no information to be gained by keeping the extra\n>>> commit around.  All rebase types have always dropped these commits, and\n>>> no one to my knowledge has ever requested that we do otherwise.\n>>>\n>>> For many of the become empty cases (and likely even most), we will also\n>>> be able to drop the commit without loss of information -- but this isn't\n>>> quite always the case.  Since these commits represent cases that were\n>>> not clean cherry-picks, there is no upstream commit message explaining\n>>> the same set of changes.  Projects with good commit message hygiene will\n>>> likely have the explanation from our commit message contained within or\n>>> spread among the relevant upstream commits, but not all projects run\n>>> that way.  As such, the commit message of the commit being rebased may\n>>> have reasoning that suggests additional changes that should be made to\n>>> adapt to the new base, or it may have information that someone wants to\n>>> add as a note to another commit, or perhaps someone even wants to create\n>>> an empty commit with the commit message as-is.\n>>>\n>>> Junio commented on the \"become-empty\" types of commits as follows[1]:\n>>>\n>>>       WRT a change that ends up being empty (as opposed to a change that\n>>>       is empty from the beginning), I'd think that the current behaviour\n>>>       is desireable one.  \"am\" based rebase is solely to transplant an\n>>>       existing history and want to stop much less than \"interactive\" one\n>>>       whose purpose is to polish a series before making it publishable,\n>>>       and asking for confirmation (\"this has become empty--do you want to\n>>>       drop it?\") is more appropriate from the workflow point of view.\n>>>\n>>> [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/\n>>>\n>>> I would simply add that his arguments for \"am\"-based rebases actually\n>>> apply to all non-explicitly-interactive rebases.  Also, since we are\n>>> stating that different cases should have different defaults, it may be\n>>> worth providing a flag to allow users to select which behavior they want\n>>> for these commits.\n>>>\n>>> Introduce a new command line flag for selecting the desired behavior:\n>>>       --empty={drop,keep,ask}\n>>> with the definitions:\n>>>       drop: drop commits which become empty\n>>>       keep: keep commits which become empty\n>>>       ask:  provide the user a chance to interact and pick what to do with\n>>>             commits which become empty on a case-by-case basis\n>>>\n>>> In line with Junio's suggestion, if the --empty flag is not specified,\n>>> pick defaults as follows:\n>>>       explicitly interactive: ask\n>>>       otherwise: drop\n>>\n>> Looking at the implementation there is a third option - if `--exec` is\n>> given without `-i` then the default is \"keep\". I'm not sure if having\n>> different defaults is convenient or confusing but don't feel that\n>> strongly about it.\n> \n> Heh, in https://lore.kernel.org/git/404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com/\n> you argued that having different defaults was confusing and sounded\n> like you felt strongly about it.  Granted, there has been a lot of\n> simplification to the implementation (and description) since then but\n> I'm still inclined to go with the simpler and more easily explained\n> behavior for the defaults based on what you said there.\n\nI would still prefer a common default but I can see the logic to the \ndefaults you are proposing I just think it makes it hard to explain and \nit'll end up surprising someone who was expecting a different default. \nWhen I said I didn't feel that strongly I meant that it wasn't worth \nholding up this just for this.\n\nBest Wishes\n\nPhillip\n\n>> I've got a few minor comments below (the mains ones\n>> are saying which commit has been dropped and testing the default\n>> behavior when --empty is not given) but basically I like the new patch.\n>> Thanks for working on this, the commit message does a good job of\n>> explaining the changes.\n> \n> :-)\n> \n>>> Signed-off-by: Elijah Newren <newren@gmail.com>\n>>> ---\n>>>    Documentation/git-rebase.txt | 27 ++++++++++++++++---\n>>>    builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++\n>>>    sequencer.c                  | 48 +++++++++++++++++++++++++--------\n>>>    sequencer.h                  |  1 +\n>>>    t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----\n>>>    t/t3427-rebase-subtree.sh    |  8 +++---\n>>>    6 files changed, 161 insertions(+), 25 deletions(-)\n>>>\n>>> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n>>> index 1d19542d79..551a91d764 100644\n>>> --- a/Documentation/git-rebase.txt\n>>> +++ b/Documentation/git-rebase.txt\n>>> @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.\n>>>        original branch. The index and working tree are also left\n>>>        unchanged as a result.\n>>>\n>>> +--empty={drop,keep,ask}::\n>>> +     How to handle commits that are not empty to start and are not\n>>> +     clean cherry-picks of any upstream commit, but which become\n>>> +     empty after rebasing (because they contain a subset of already\n>>> +     upstream changes).  With drop (the default), commits that\n>>> +     become empty are dropped.  With keep, such commits are kept.\n>>> +     With ask (implied by --interactive), the rebase will halt when\n>>> +     an empty commit is applied allowing you to choose whether to\n>>> +     drop it, edit files more, or just commit the empty changes.\n>>\n>> We should probably document the default for --exec without -i\n> \n> I did, but I guess it wasn't clear enough.  Maybe I should add \"Note\n> that other options like --exec will use the default of drop unless\n> -i/--interactive is specified.\"?\n> \n>>> +Note that commits which start empty are kept, and commits which are\n>>> +clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n>>> +always dropped.\n>>> ++\n>>> +See also INCOMPATIBLE OPTIONS below.\n>>> +\n>>>    --keep-empty::\n>>>        No-op.  Rebasing commits that started empty (had no change\n>>>        relative to their parent) used to fail and this option would\n>>> @@ -561,6 +577,7 @@ are incompatible with the following options:\n>>>     * --interactive\n>>>     * --exec\n>>>     * --keep-empty\n>>> + * --empty=\n>>>     * --edit-todo\n>>>     * --root when used in combination with --onto\n>>>\n>>> @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:\n>>>     * --preserve-merges and --interactive\n>>>     * --preserve-merges and --signoff\n>>>     * --preserve-merges and --rebase-merges\n>>> + * --preserve-merges and --empty=\n>>>     * --keep-base and --onto\n>>>     * --keep-base and --root\n>>>\n>>> @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It\n>>>    also drops commits that become empty and has no option for controlling\n>>>    this behavior.\n>>>\n>>> -The interactive backend keeps intentionally empty commits.\n>>> -Unfortunately, it always halts whenever it runs across a commit that\n>>> -becomes empty, even when the rebase is not explicitly interactive.\n>>> +The interactive backend keeps intentionally empty commits.  Similar to\n>>> +the am backend, by default the interactive backend drops commits that\n>>> +become empty unless -i/--interactive is specified (in which case it\n>>> +stops and asks the user what to do).  The interactive backend also has\n>>> +an --empty={drop,keep,ask} option for changing the behavior of\n>>> +handling commits that become empty.\n>>>\n>>>    Directory rename detection\n>>>    ~~~~~~~~~~~~~~~~~~~~~~~~~~\n>>> diff --git a/builtin/rebase.c b/builtin/rebase.c\n>>> index 537b3241ce..c299869e7b 100644\n>>> --- a/builtin/rebase.c\n>>> +++ b/builtin/rebase.c\n>>> @@ -50,8 +50,16 @@ enum rebase_type {\n>>>        REBASE_PRESERVE_MERGES\n>>>    };\n>>>\n>>> +enum empty_type {\n>>> +     EMPTY_UNSPECIFIED = -1,\n>>> +     EMPTY_DROP,\n>>> +     EMPTY_KEEP,\n>>> +     EMPTY_ASK\n>>> +};\n>>> +\n>>>    struct rebase_options {\n>>>        enum rebase_type type;\n>>> +     enum empty_type empty;\n>>>        const char *state_dir;\n>>>        struct commit *upstream;\n>>>        const char *upstream_name;\n>>> @@ -91,6 +99,7 @@ struct rebase_options {\n>>>\n>>>    #define REBASE_OPTIONS_INIT {                               \\\n>>>                .type = REBASE_UNSPECIFIED,             \\\n>>> +             .empty = EMPTY_UNSPECIFIED,             \\\n>>>                .flags = REBASE_NO_QUIET,               \\\n>>>                .git_am_opts = ARGV_ARRAY_INIT,         \\\n>>>                .git_format_patch_opt = STRBUF_INIT     \\\n>>> @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>>>                replay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n>>>        replay.allow_empty = 1;\n>>>        replay.allow_empty_message = opts->allow_empty_message;\n>>> +     replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);\n>>> +     replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);\n>>>        replay.verbose = opts->flags & REBASE_VERBOSE;\n>>>        replay.reschedule_failed_exec = opts->reschedule_failed_exec;\n>>>        replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n>>> @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,\n>>>\n>>>        BUG_ON_OPT_ARG(arg);\n>>>\n>>> +     /*\n>>> +      * If we ever want to remap --keep-empty to --empty=keep, insert:\n>>> +      *      opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;\n>>> +      */\n>>>        opts->type = REBASE_INTERACTIVE;\n>>>        return 0;\n>>>    }\n>>> @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,\n>>>        return 0;\n>>>    }\n>>>\n>>> +static enum empty_type parse_empty_value(const char *value)\n>>> +{\n>>> +     if (!strcasecmp(value, \"drop\"))\n>>> +             return EMPTY_DROP;\n>>> +     else if (!strcasecmp(value, \"keep\"))\n>>> +             return EMPTY_KEEP;\n>>> +     else if (!strcasecmp(value, \"ask\"))\n>>> +             return EMPTY_ASK;\n>>> +\n>>> +     die(_(\"unrecognized empty type '%s'; valid values are \\\"drop\\\", \\\"keep\\\", and \\\"ask\\\".\"), value);\n>>> +}\n>>> +\n>>> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)\n>>> +{\n>>> +     struct rebase_options *options = opt->value;\n>>> +     enum empty_type value = parse_empty_value(arg);\n>>> +\n>>> +     BUG_ON_OPT_NEG(unset);\n>>> +\n>>> +     options->empty = value;\n>>> +     return 0;\n>>> +}\n>>> +\n>>>    static void NORETURN error_on_missing_default_upstream(void)\n>>>    {\n>>>        struct branch *current_branch = branch_get(NULL);\n>>> @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>>                                 \"ignoring them\"),\n>>>                              REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>>>                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n>>> +             OPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n>>> +                            N_(\"how to handle empty commits\"),\n>>\n>> Maybe we should say \"how to handle commits that become empty\" to\n>> distinguish them from commits that start empty which we always keep\n> \n> Ooh, good catch; will fix.\n> \n>>> +                            PARSE_OPT_NONEG, parse_opt_empty),\n>>>                { OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>>>                        N_(\"(DEPRECATED) keep empty commits\"),\n>>>                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,\n>>> @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>>        if (!(options.flags & REBASE_NO_QUIET))\n>>>                argv_array_push(&options.git_am_opts, \"-q\");\n>>>\n>>> +     if (options.empty != EMPTY_UNSPECIFIED)\n>>> +             imply_interactive(&options, \"--empty\");\n>>> +\n>>>        if (gpg_sign) {\n>>>                free(options.gpg_sign_opt);\n>>>                options.gpg_sign_opt = xstrfmt(\"-S%s\", gpg_sign);\n>>> @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>>                break;\n>>>        }\n>>>\n>>> +     if (options.empty == EMPTY_UNSPECIFIED) {\n>>> +             if (options.flags & REBASE_INTERACTIVE_EXPLICIT)\n>>> +                     options.empty = EMPTY_ASK;\n>>> +             else if (exec.nr > 0)\n>>> +                     options.empty = EMPTY_KEEP;\n>>> +             else\n>>> +                     options.empty = EMPTY_DROP;\n>>> +     }\n>>>        if (reschedule_failed_exec > 0 && !is_interactive(&options))\n>>>                die(_(\"--reschedule-failed-exec requires \"\n>>>                      \"--exec or --interactive\"));\n>>> diff --git a/sequencer.c b/sequencer.c\n>>> index c21fc202b1..354d0b5a38 100644\n>>> --- a/sequencer.c\n>>> +++ b/sequencer.c\n>>> @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, \"rebase-merge/strategy\")\n>>>    static GIT_PATH_FUNC(rebase_path_strategy_opts, \"rebase-merge/strategy_opts\")\n>>>    static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, \"rebase-merge/allow_rerere_autoupdate\")\n>>>    static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedule-failed-exec\")\n>>> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n>>> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n>>>\n>>>    static int git_sequencer_config(const char *k, const char *v, void *cb)\n>>>    {\n>>> @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)\n>>>    }\n>>>\n>>>    /*\n>>> - * Do we run \"git commit\" with \"--allow-empty\"?\n>>> + * Should empty commits be allowed?  Return status:\n>>> + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)\n>>> + *     0: Halt on empty commit\n>>> + *     1: Allow empty commit\n>>> + *     2: Drop empty commit\n>>>     */\n>>>    static int allow_empty(struct repository *r,\n>>>                       struct replay_opts *opts,\n>>> @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,\n>>>        int index_unchanged, originally_empty;\n>>>\n>>>        /*\n>>> -      * Three cases:\n>>> +      * Four cases:\n>>>         *\n>>>         * (1) we do not allow empty at all and error out.\n>>>         *\n>>> -      * (2) we allow ones that were initially empty, but\n>>> -      * forbid the ones that become empty;\n>>> +      * (2) we allow ones that were initially empty, and\n>>> +      *     just drop the ones that become empty\n>>>         *\n>>> -      * (3) we allow both.\n>>> +      * (3) we allow ones that were initially empty, but\n>>> +      *     halt for the ones that become empty;\n>>> +      *\n>>> +      * (4) we allow both.\n>>>         */\n>>>        if (!opts->allow_empty)\n>>>                return 0; /* let \"git commit\" barf as necessary */\n>>> @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,\n>>>        originally_empty = is_original_commit_empty(commit);\n>>>        if (originally_empty < 0)\n>>>                return originally_empty;\n>>> -     if (!originally_empty)\n>>> -             return 0;\n>>> -     else\n>>> +     if (originally_empty)\n>>>                return 1;\n>>> +     else if (opts->drop_redundant_commits)\n>>> +             return 2;\n>>> +     else\n>>> +             return 0;\n>>>    }\n>>>\n>>>    static struct {\n>>> @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,\n>>>        char *author = NULL;\n>>>        struct commit_message msg = { NULL, NULL, NULL, NULL };\n>>>        struct strbuf msgbuf = STRBUF_INIT;\n>>> -     int res, unborn = 0, reword = 0, allow;\n>>> +     int res, unborn = 0, reword = 0, allow, drop_commit;\n>>>\n>>>        if (opts->no_commit) {\n>>>                /*\n>>> @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,\n>>>                goto leave;\n>>>        }\n>>>\n>>> +     drop_commit = 0;\n>>>        allow = allow_empty(r, opts, commit);\n>>>        if (allow < 0) {\n>>>                res = allow;\n>>>                goto leave;\n>>> -     } else if (allow)\n>>> +     } else if (allow == 1) {\n>>>                flags |= ALLOW_EMPTY;\n>>> -     if (!opts->no_commit) {\n>>> +     } else if (allow == 2) {\n>>> +             drop_commit = 1;\n>>> +             fprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n>>\n>> nit pick - usually messages start with a lowercase letter. Would it be\n>> helpful to explicitly state which commit is being dropped as well as\n>> why? Something like\n>>     dropping <oid> <subject> - patch contents already upstream\n> \n> I was actually just trying to mimic the am-backend here, and copied\n> its message verbatim for this case (see am_run() in builtin/am.c).\n> However, your version does seem more helpful and informative.  I'll\n> look into it implementing it here.\n> \n>>\n>>> +     } // else allow == 0 and there's nothing special to do\n>>\n>> We don't use // for comments\n> \n> Oops, sorry.  Will fix.\n> \n>>\n>>> +     if (!opts->no_commit && !drop_commit) {\n>>>                if (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>>>                        res = do_commit(r, msg_file, author, opts, flags);\n>>>                else\n>>> @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)\n>>>                if (file_exists(rebase_path_reschedule_failed_exec()))\n>>>                        opts->reschedule_failed_exec = 1;\n>>>\n>>> +             if (file_exists(rebase_path_drop_redundant_commits()))\n>>> +                     opts->drop_redundant_commits = 1;\n>>> +\n>>> +             if (file_exists(rebase_path_keep_redundant_commits()))\n>>> +                     opts->keep_redundant_commits = 1;\n>>> +\n>>>                read_strategy_opts(opts, &buf);\n>>>                strbuf_release(&buf);\n>>>\n>>> @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n>>>                write_file(rebase_path_gpg_sign_opt(), \"-S%s\\n\", opts->gpg_sign);\n>>>        if (opts->signoff)\n>>>                write_file(rebase_path_signoff(), \"--signoff\\n\");\n>>> +     if (opts->drop_redundant_commits)\n>>> +             write_file(rebase_path_drop_redundant_commits(), \"%s\", \"\");\n>>> +     if (opts->keep_redundant_commits)\n>>> +             write_file(rebase_path_keep_redundant_commits(), \"%s\", \"\");\n>>>        if (opts->reschedule_failed_exec)\n>>>                write_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n>>>\n>>> diff --git a/sequencer.h b/sequencer.h\n>>> index c165e0ff25..3b0ab9141f 100644\n>>> --- a/sequencer.h\n>>> +++ b/sequencer.h\n>>> @@ -39,6 +39,7 @@ struct replay_opts {\n>>>        int allow_rerere_auto;\n>>>        int allow_empty;\n>>>        int allow_empty_message;\n>>> +     int drop_redundant_commits;\n>>>        int keep_redundant_commits;\n>>>        int verbose;\n>>>        int quiet;\n>>> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\n>>> index 22d97e143b..dcb4cb4751 100755\n>>> --- a/t/t3424-rebase-empty.sh\n>>> +++ b/t/t3424-rebase-empty.sh\n>>> @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '\n>>>        git commit -m \"Five letters ought to be enough for anybody\"\n>>>    '\n>>>\n>>> -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n>>> +test_expect_failure 'rebase (am-backend)' '\n>>>        test_when_finished \"git rebase --abort\" &&\n>>>        git checkout -B testing localmods &&\n>>>        # rebase (--am) should not drop commits that start empty\n>>> @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '\n>>>        test_cmp expect actual\n>>>    '\n>>>\n>>> -test_expect_failure 'rebase --merge with a variety of empty commits' '\n>>> -     test_when_finished \"git rebase --abort\" &&\n>>> +test_expect_success 'rebase --merge --empty=drop' '\n>>>        git checkout -B testing localmods &&\n>>> -     # rebase --merge should not halt on the commit that becomes empty\n>>> -     git rebase --merge upstream &&\n>>> +     git rebase --merge --empty=drop upstream &&\n>>> +\n>>> +     test_write_lines D C B A >expect &&\n>>> +     git log --format=%s >actual &&\n>>> +     test_cmp expect actual\n>>> +'\n>>> +\n>>> +test_expect_success 'rebase --merge --empty=keep' '\n>>> +     git checkout -B testing localmods &&\n>>> +     git rebase --merge --empty=keep upstream &&\n>>> +\n>>> +     test_write_lines D C2 C B A >expect &&\n>>> +     git log --format=%s >actual &&\n>>> +     test_cmp expect actual\n>>> +'\n>>> +\n>>> +test_expect_success 'rebase --merge --empty=ask' '\n>>> +     git checkout -B testing localmods &&\n>>> +     test_must_fail git rebase --merge --empty=ask upstream &&\n>>> +\n>>> +     git rebase --skip &&\n>>>\n>>>        test_write_lines D C B A >expect &&\n>>>        git log --format=%s >actual &&\n>>> @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '\n>>>\n>>>    GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>>>\n>>> -test_expect_success 'rebase --interactive with a variety of empty commits' '\n>>> +test_expect_success 'rebase --interactive --empty=drop' '\n>>> +     git checkout -B testing localmods &&\n>>> +     git rebase --interactive --empty=drop upstream &&\n>>> +\n>>> +     test_write_lines D C B A >expect &&\n>>> +     git log --format=%s >actual &&\n>>> +     test_cmp expect actual\n>>> +'\n>>> +\n>>> +test_expect_success 'rebase --interactive --empty=keep' '\n>>> +     git checkout -B testing localmods &&\n>>> +     git rebase --interactive --empty=keep upstream &&\n>>> +\n>>> +     test_write_lines D C2 C B A >expect &&\n>>> +     git log --format=%s >actual &&\n>>> +     test_cmp expect actual\n>>> +'\n>>> +\n>>> +test_expect_success 'rebase --interactive --empty=ask' '\n>>>        git checkout -B testing localmods &&\n>>> -     test_must_fail git rebase --interactive upstream &&\n>>> +     test_must_fail git rebase --interactive --empty=ask upstream &&\n>>>\n>>>        git rebase --skip &&\n>>\n>> As the default if --empty is not given is supposed to vary depending on\n>> the other options given it would be good to test that I think\n> \n> I'll add some tests.\n> \n> \n> Thanks for the thorough review!\n> Elijah\n> \n"},{"id":"391872","messageId":"e2863381-174c-a55c-bb22-0c7aec9cabf4@gmail.com","threadId":"52496","inReplyTo":"pull.679.v5.git.git.1581802602.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/20] rebase: make the default backend configurable and change the default","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-02-16T15:01:51Z","receivedAt":"2020-02-16T15:01:58Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 15/02/2020 21:36, Elijah Newren via GitGitGadget wrote:\n> This is v5 of en/rebase-backend based on v2.25.0. This series does a lot of\n> work around making the default rebase backend configurable, and switches the\n> default from the apply backend to the merge one.\n> \n> Changes since v4:\n> \n>   * Added a note about an unfortunately-timed Ctrl-C to the docs, as\n>     suggested by SZEDER.\n\nThat's a good idea - I don't know why builtin/rebase.c only calls \nrebase_write_basic_state() which writes head-name after it has run \nformat-patch and am. We write the patches to the state directory so it's \nnot like it saves us from having to clean up.\n\n>   * Addressed feedback from Phillip\n>   * Added a new patch at the end renaming the two backends. PLEASE take a\n>     look at its commit message for rationale and let me know if you agree\n>     with it.\n\nI had a look and I think it is a good idea - merge and apply make much \nmore sense that interactive and am\n\n> Note that in particular, the last patch at the end could have been partially\n> squashed into other patches, but if people objected to the patch in general\n> I wanted Junio to be able to take the rest of the series and just drop that\n> patch.\n> \n> As with v4, there are some things mentioned earlier that are not addressed:\n> \n>   * I did not remove the --am (now --apply) option as suggested by Phillip,\n>     since Junio and Phillip were still discussing whether it is\n>     wanted/needed. I kind of like having it, but have no strong opinion.\n>   * I noted the post-commit hook in the differences between backends. Emily\n>     is investigating what changes need to happen there, so I merely\n>     documented the existing differences.\n>   * I did not address the last two items Jonathan brought up as I couldn't\n>     find enough information to reproduce or understand the problems.\n> \n> Elijah Newren (20):\n>    git-rebase.txt: update description of --allow-empty-message\n>    t3404: directly test the behavior of interest\n>    rebase (interactive-backend): make --keep-empty the default\n>    rebase (interactive-backend): fix handling of commits that become\n>      empty\n>    t3406: simplify an already simple test\n>    rebase, sequencer: remove the broken GIT_QUIET handling\n>    rebase: make sure to pass along the quiet flag to the sequencer\n>    rebase: fix handling of restrict_revision\n>    t3432: make these tests work with either am or merge backends\n>    rebase: allow more types of rebases to fast-forward\n>    git-rebase.txt: add more details about behavioral differences of\n>      backends\n>    rebase: move incompatibility checks between backend options a bit\n>      earlier\n>    rebase: add an --am option\n>    git-prompt: change the prompt for interactive-based rebases\n>    rebase: drop '-i' from the reflog for interactive-based rebases\n>    rebase tests: mark tests specific to the am-backend with --am\n>    rebase tests: repeat some tests using the merge backend instead of am\n>    rebase: make the backend configurable via config setting\n>    rebase: change the default backend from \"am\" to \"merge\"\n>    rebase: rename the two primary rebase backends\n> \n>   Documentation/config/rebase.txt        |   6 +\n>   Documentation/git-rebase.txt           | 170 +++++++++++++---\n>   builtin/rebase.c                       | 257 +++++++++++++++++--------\n>   contrib/completion/git-prompt.sh       |   6 +-\n>   rebase-interactive.c                   |   7 +-\n>   rebase-interactive.h                   |   2 +-\n>   sequencer.c                            |  82 +++++---\n>   sequencer.h                            |   3 +-\n>   t/t3400-rebase.sh                      |  40 +++-\n>   t/t3401-rebase-and-am-rename.sh        |   4 +-\n>   t/t3404-rebase-interactive.sh          |  19 +-\n>   t/t3406-rebase-message.sh              |  19 +-\n>   t/t3407-rebase-abort.sh                |   6 +-\n>   t/t3420-rebase-autostash.sh            |  22 +--\n>   t/t3421-rebase-topology-linear.sh      |  48 ++---\n>   t/t3424-rebase-empty.sh                | 126 ++++++++++++\n>   t/t3425-rebase-topology-merges.sh      |   8 +-\n>   t/t3427-rebase-subtree.sh              |  12 +-\n>   t/t3432-rebase-fast-forward.sh         |  54 +++---\n>   t/t5407-post-rewrite-hook.sh           |  16 +-\n>   t/t5520-pull.sh                        |  27 ++-\n>   t/t6047-diff3-conflict-markers.sh      |  13 +-\n>   t/t7512-status-help.sh                 |  12 +-\n>   t/t9106-git-svn-commit-diff-clobber.sh |   3 +-\n>   t/t9903-bash-prompt.sh                 |   8 +-\n>   25 files changed, 693 insertions(+), 277 deletions(-)\n>   create mode 100755 t/t3424-rebase-empty.sh\n> \n> \n> base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5\n> Pull-Request: https://github.com/git/git/pull/679\n> \n> Range-diff vs v4:\n> \n>    1:  3ea48d53940 =  1:  3ea48d53940 git-rebase.txt: update description of --allow-empty-message\n>    2:  10fdd162a05 =  2:  10fdd162a05 t3404: directly test the behavior of interest\n>    3:  179f82ab83e !  3:  74cab9a32b3 rebase (interactive-backend): make --keep-empty the default\n>       @@ -4,13 +4,13 @@\n>        \n>            Different rebase backends have different treatment for commits which\n>            start empty (i.e. have no changes relative to their parent), and the\n>       -    --keep-empty option was added at some point to allow adjusting behavior\n>       -    for the interactive backend.  The handling of commits which start empty\n>       -    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make\n>       -    --allow-empty-message the default, 2018-06-27), which pointed out that\n>       -    the behavior for various backends is often more happenstance than\n>       -    design.  The specific change made in that commit is actually quite\n>       -    relevant as well and much of the logic there directly applies here.\n>       +    --keep-empty option was added at some point to allow adjusting behavior.\n>       +    The handling of commits which start empty is actually quite similar to\n>       +    commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,\n>       +    2018-06-27), which pointed out that the behavior for various backends is\n>       +    often more happenstance than design.  The specific change made in that\n>       +    commit is actually quite relevant as well and much of the logic there\n>       +    directly applies here.\n\nThanks for rewording this I think the new version is better for omitting \nthe reference to the interactive backend. The interdiff for this patch \nand the next look good.\n\nThanks for working on this - it's turned into quite a large series\n\nBest Wishes\n\nPhillip\n\n>            It makes a lot of sense in 'git commit' to error out on the creation of\n>            empty commits, unless an override flag is provided.  However, once\n>       @@ -405,8 +405,6 @@\n>        +\ttest_cmp expect actual\n>        +'\n>        +\n>       -+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>       -+\n>        +test_expect_success 'rebase --interactive with a variety of empty commits' '\n>        +\tgit checkout -B testing localmods &&\n>        +\ttest_must_fail git rebase --interactive upstream &&\n>    4:  c9542a2abe0 !  4:  54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty\n>       @@ -98,6 +98,8 @@\n>        +\tWith ask (implied by --interactive), the rebase will halt when\n>        +\tan empty commit is applied allowing you to choose whether to\n>        +\tdrop it, edit files more, or just commit the empty changes.\n>       ++\tOther options, like --exec, will use the default of drop unless\n>       ++\t-i/--interactive is explicitly specified.\n>        ++\n>        +Note that commits which start empty are kept, and commits which are\n>        +clean cherry-picks (as determined by `git log --cherry-mark ...`) are\n>       @@ -224,7 +226,7 @@\n>         \t\t\t      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),\n>         \t\tOPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),\n>        +\t\tOPT_CALLBACK_F(0, \"empty\", &options, N_(\"{drop,keep,ask}\"),\n>       -+\t\t\t       N_(\"how to handle empty commits\"),\n>       ++\t\t\t       N_(\"how to handle commits that become empty\"),\n>        +\t\t\t       PARSE_OPT_NONEG, parse_opt_empty),\n>         \t\t{ OPTION_CALLBACK, 'k', \"keep-empty\", &options, NULL,\n>         \t\t\tN_(\"(DEPRECATED) keep empty commits\"),\n>       @@ -342,8 +344,10 @@\n>        -\tif (!opts->no_commit) {\n>        +\t} else if (allow == 2) {\n>        +\t\tdrop_commit = 1;\n>       -+\t\tfprintf(stderr, _(\"No changes -- Patch already applied.\\n\"));\n>       -+\t} // else allow == 0 and there's nothing special to do\n>       ++\t\tfprintf(stderr,\n>       ++\t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n>       ++\t\t\toid_to_hex(&commit->object.oid), msg.subject);\n>       ++\t} /* else allow == 0 and there's nothing special to do */\n>        +\tif (!opts->no_commit && !drop_commit) {\n>         \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>         \t\t\tres = do_commit(r, msg_file, author, opts, flags);\n>       @@ -404,9 +408,7 @@\n>        -test_expect_failure 'rebase --merge with a variety of empty commits' '\n>        -\ttest_when_finished \"git rebase --abort\" &&\n>        +test_expect_success 'rebase --merge --empty=drop' '\n>       - \tgit checkout -B testing localmods &&\n>       --\t# rebase --merge should not halt on the commit that becomes empty\n>       --\tgit rebase --merge upstream &&\n>       ++\tgit checkout -B testing localmods &&\n>        +\tgit rebase --merge --empty=drop upstream &&\n>        +\n>        +\ttest_write_lines D C B A >expect &&\n>       @@ -414,6 +416,17 @@\n>        +\ttest_cmp expect actual\n>        +'\n>        +\n>       ++test_expect_success 'rebase --merge uses default of --empty=drop' '\n>       + \tgit checkout -B testing localmods &&\n>       +-\t# rebase --merge should not halt on the commit that becomes empty\n>       + \tgit rebase --merge upstream &&\n>       +\n>       + \ttest_write_lines D C B A >expect &&\n>       +@@\n>       + \ttest_cmp expect actual\n>       + '\n>       +\n>       +-test_expect_success 'rebase --interactive with a variety of empty commits' '\n>        +test_expect_success 'rebase --merge --empty=keep' '\n>        +\tgit checkout -B testing localmods &&\n>        +\tgit rebase --merge --empty=keep upstream &&\n>       @@ -428,14 +441,12 @@\n>        +\ttest_must_fail git rebase --merge --empty=ask upstream &&\n>        +\n>        +\tgit rebase --skip &&\n>       -\n>       - \ttest_write_lines D C B A >expect &&\n>       - \tgit log --format=%s >actual &&\n>       -@@\n>       -\n>       - GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR\n>       -\n>       --test_expect_success 'rebase --interactive with a variety of empty commits' '\n>       ++\n>       ++\ttest_write_lines D C B A >expect &&\n>       ++\tgit log --format=%s >actual &&\n>       ++\ttest_cmp expect actual\n>       ++'\n>       ++\n>        +test_expect_success 'rebase --interactive --empty=drop' '\n>        +\tgit checkout -B testing localmods &&\n>        +\tgit rebase --interactive --empty=drop upstream &&\n>       @@ -455,11 +466,19 @@\n>        +'\n>        +\n>        +test_expect_success 'rebase --interactive --empty=ask' '\n>       - \tgit checkout -B testing localmods &&\n>       --\ttest_must_fail git rebase --interactive upstream &&\n>       ++\tgit checkout -B testing localmods &&\n>        +\ttest_must_fail git rebase --interactive --empty=ask upstream &&\n>       -\n>       - \tgit rebase --skip &&\n>       ++\n>       ++\tgit rebase --skip &&\n>       ++\n>       ++\ttest_write_lines D C B A >expect &&\n>       ++\tgit log --format=%s >actual &&\n>       ++\ttest_cmp expect actual\n>       ++'\n>       ++\n>       ++test_expect_success 'rebase --interactive uses default of --empty=ask' '\n>       + \tgit checkout -B testing localmods &&\n>       + \ttest_must_fail git rebase --interactive upstream &&\n>         \n>        \n>         diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh\n>    5:  9f66229d5cc =  5:  f5170e71510 t3406: simplify an already simple test\n>    6:  8d731fa39c3 =  6:  2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling\n>    7:  b6b6597eef8 =  7:  3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer\n>    8:  0acefa988b1 =  8:  4e6f5ce8c46 rebase: fix handling of restrict_revision\n>    9:  8c5b5b51330 =  9:  bcd04ec4ded t3432: make these tests work with either am or merge backends\n>   10:  b8c087d6fb1 = 10:  45eb87b36b0 rebase: allow more types of rebases to fast-forward\n>   11:  b50a1741e0b = 11:  11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends\n>   12:  58e6e4ffb3f = 12:  0c921919dce rebase: move incompatibility checks between backend options a bit earlier\n>   13:  5478c730aca = 13:  de1b0aaca88 rebase: add an --am option\n>   14:  db5e29bd818 = 14:  e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases\n>   15:  413e190ac9a ! 15:  f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases\n>       @@ -54,17 +54,6 @@\n>         \t\t */\n>         \t\treturn error(_(\"%s: Unable to write new index file\"),\n>         \t\t\t_(action_name(opts)));\n>       -@@\n>       - \tint next = todo_list->current, offset, fd;\n>       -\n>       - \t/*\n>       --\t * rebase -i writes \"git-rebase-todo\" without the currently executing\n>       --\t * command, appending it to \"done\" instead.\n>       -+\t * interactive backend writes \"git-rebase-todo\" without the currently\n>       -+\t * executing command, appending it to \"done\" instead.\n>       - \t */\n>       - \tif (is_rebase_i(opts))\n>       - \t\tnext++;\n>        @@\n>         \t\treturn error(_(\"illegal label name: '%.*s'\"), len, name);\n>         \n>   16:  170be283a85 ! 16:  56486d4d694 rebase tests: mark tests specific to the am-backend with --am\n>       @@ -173,6 +173,163 @@\n>         testrebase \" --interactive\" .git/rebase-merge\n>         \n>        \n>       + diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\n>       + --- a/t/t3421-rebase-topology-linear.sh\n>       + +++ b/t/t3421-rebase-topology-linear.sh\n>       +@@\n>       + \t\ttest_linear_range 'd e' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_cmp_rev e HEAD\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'd e' b..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success --fork-point\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       +@@\n>       + \t\ttest_linear_range 'd e' branch-b..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success --fork-point\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       +@@\n>       + \t\ttest_cmp_rev e HEAD\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success --fork-point\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       +@@\n>       + \t\ttest_linear_range 'd i' h..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'd' h..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'd i' f..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'd gp i' h..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'j d k l' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase failure ''\n>       ++test_run_rebase failure --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase failure -p\n>       +@@\n>       + \t\ttest_linear_range 'd k l' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'd k l' j..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'x y' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\ttest_linear_range 'x y' c..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase failure -p\n>       +@@\n>       + \t\ttest_linear_range 'x y' m..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase success -p\n>       +@@\n>       + \t\"\n>       + }\n>       +\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase failure -p\n>       +@@\n>       + \t\ttest_linear_range 'x y' m..\n>       + \t\"\n>       + }\n>       +-test_run_rebase success ''\n>       ++test_run_rebase success --am\n>       + test_run_rebase success -m\n>       + test_run_rebase success -i\n>       + test_have_prereq !REBASE_P || test_run_rebase failure -p\n>       +\n>         diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh\n>         --- a/t/t3425-rebase-topology-merges.sh\n>         +++ b/t/t3425-rebase-topology-merges.sh\n>   17:  1e3d4066c40 = 17:  dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am\n>   18:  9b4ac83d2d0 = 18:  ba1f2699b39 rebase: make the backend configurable via config setting\n>   19:  859a4a94d7f ! 19:  59faaa2f920 rebase: change the default backend from \"am\" to \"merge\"\n>       @@ -13,6 +13,7 @@\n>                patches incorrectly.[2]\n>              * lack of access to original commits means that conflict marker\n>                annotation has less information available.\n>       +      * the am backend has safety problems with an ill-timed interrupt.\n>        \n>            Also, the merge/interactive backend have far more abilities, appear to\n>            currently have a slight performance advantage[3] and have room for more\n>       @@ -38,6 +39,24 @@\n>         +\n>         Note that a rebase merge works by replaying each commit from the working\n>         branch on top of the <upstream> branch.  Because of this, when a merge\n>       +@@\n>       + should have the same behavior, though it is not clear which one is\n>       + correct.\n>       +\n>       ++Interruptability\n>       ++~~~~~~~~~~~~~~~~\n>       ++\n>       ++The am backend has safety problems with an ill-timed interrupt; if the\n>       ++user presses Ctrl-C at the wrong time to try to abort the rebase, the\n>       ++rebase can enter a state where it cannot be aborted with a subsequent\n>       ++`git rebase --abort`.  The interactive backend does not appear to\n>       ++suffer from the same shortcoming.  (See\n>       ++https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for\n>       ++details.)\n>       ++\n>       + Miscellaneous differences\n>       + ~~~~~~~~~~~~~~~~~~~~~~~~~\n>       +\n>        \n>         diff --git a/builtin/rebase.c b/builtin/rebase.c\n>         --- a/builtin/rebase.c\n>    -:  ----------- > 20:  ad8339aebf2 rebase: rename the two primary rebase backends\n> \n"},{"id":"393131","messageId":"20200312151318.GM212281@google.com","threadId":"52496","inReplyTo":"ad8339aebf28ec84c22ed59cef06614d204adb55.1581802602.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-03-12T15:13:18Z","receivedAt":"2020-03-12T15:13:28Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Sat, Feb 15, 2020 at 09:36:41PM +0000, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Two related changes, with separate rationale for each:\n> \n> Rename the 'interactive' backend to 'merge' because:\n>   * 'interactive' as a name caused confusion; this backend has been used\n>     for many kinds of non-interactive rebases, and will probably be used\n>     in the future for more non-interactive rebases than interactive ones\n>     given that we are making it the default.\n>   * 'interactive' is not the underlying strategy; merging is.\n>   * the directory where state is stored is not called\n>     .git/rebase-interactive but .git/rebase-merge.\n> \n> Rename the 'am' backend to 'apply' because:\n>   * Few users are familiar with git-am as a reference point.\n>   * Related to the above, the name 'am' makes sentences in the\n>     documentation harder for users to read and comprehend (they may read\n>     it as the verb from \"I am\"); avoiding this difficult places a large\n>     burden on anyone writing documentation about this backend to be very\n>     careful with quoting and sentence structure and often forces\n>     annoying redundancy to try to avoid such problems.\n>   * Users stumble over pronunciation (\"am\" as in \"I am a person not a\n>     backend\" or \"am\" as in \"the first and thirteenth letters in the\n>     alphabet in order are \"A-M\"); this may drive confusion when one user\n>     tries to explain to another what they are doing.\n>   * While \"am\" is the tool driving this backend, the tool driving git-am\n>     is git-apply, and since we are driving towards lower-level tools\n>     for the naming of the merge backend we may as well do so here too.\n>   * The directory where state is stored has never been called\n>     .git/rebase-am, it was always called .git/rebase-apply.\n> \n> For all the reasons listed above:\n>   * Modify the documentation to refer to the backends with the new names\n>   * Provide a brief note in the documentation connecting the new names\n>     to the old names in case users run across the old names anywhere\n>     (e.g. in old release notes or older versions of the documentation)\n>   * Change the (new) --am command line flag to --apply\n>   * Rename some enums, variables, and functions to reinforce the new\n>     backend names for us as well.\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n\nHi,\n\nThis broke quite a few upstream users for us today when we rolled out a\nnext with this patch added on top. To shim around the post-commit hook\nissue, we had set a system config for all our users to use\nmerge.backend=am; the machinery is pretty intolerant to a wrongly\nconfigured backend name (die() rather than a warning and fallback).\n\nWould it make more sense to deal with an unrecognized backend by falling\nback to the default backend, instead?\n\n>  \tif (options.type == REBASE_UNSPECIFIED) {\n>  \t\tif (!strcmp(options.default_backend, \"merge\"))\n> -\t\t\timply_interactive(&options, \"--merge\");\n> -\t\telse if (!strcmp(options.default_backend, \"am\"))\n> -\t\t\toptions.type = REBASE_AM;\n> +\t\t\timply_merge(&options, \"--merge\");\n> +\t\telse if (!strcmp(options.default_backend, \"apply\"))\n> +\t\t\toptions.type = REBASE_APPLY;\n>  \t\telse\n>  \t\t\tdie(_(\"Unknown rebase backend: %s\"),\n>  \t\t\t    options.default_backend);\n\nAt the very least, can this die() explain that it found that string in\nthe config so the user can have a guess as to how to fix it?\n\n(I realize the complained code is from earlier in the series, but this\npatch - renaming something that used to be valid without a fallback -\ninvalidated our configs, highlighting the problem for us. So I'm\nreplying here instead.)\n\n - Emily\n\n"},{"id":"393134","messageId":"CABPp-BHyNvxQZ5q=9WXXESTPmxFe4fAiE5roGeV2H+XJ_cpDmg@mail.gmail.com","threadId":"52496","inReplyTo":"20200312151318.GM212281@google.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-12T16:33:02Z","receivedAt":"2020-03-12T16:33:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> On Sat, Feb 15, 2020 at 09:36:41PM +0000, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Two related changes, with separate rationale for each:\n> >\n> > Rename the 'interactive' backend to 'merge' because:\n> >   * 'interactive' as a name caused confusion; this backend has been used\n> >     for many kinds of non-interactive rebases, and will probably be used\n> >     in the future for more non-interactive rebases than interactive ones\n> >     given that we are making it the default.\n> >   * 'interactive' is not the underlying strategy; merging is.\n> >   * the directory where state is stored is not called\n> >     .git/rebase-interactive but .git/rebase-merge.\n> >\n> > Rename the 'am' backend to 'apply' because:\n> >   * Few users are familiar with git-am as a reference point.\n> >   * Related to the above, the name 'am' makes sentences in the\n> >     documentation harder for users to read and comprehend (they may read\n> >     it as the verb from \"I am\"); avoiding this difficult places a large\n> >     burden on anyone writing documentation about this backend to be very\n> >     careful with quoting and sentence structure and often forces\n> >     annoying redundancy to try to avoid such problems.\n> >   * Users stumble over pronunciation (\"am\" as in \"I am a person not a\n> >     backend\" or \"am\" as in \"the first and thirteenth letters in the\n> >     alphabet in order are \"A-M\"); this may drive confusion when one user\n> >     tries to explain to another what they are doing.\n> >   * While \"am\" is the tool driving this backend, the tool driving git-am\n> >     is git-apply, and since we are driving towards lower-level tools\n> >     for the naming of the merge backend we may as well do so here too.\n> >   * The directory where state is stored has never been called\n> >     .git/rebase-am, it was always called .git/rebase-apply.\n> >\n> > For all the reasons listed above:\n> >   * Modify the documentation to refer to the backends with the new names\n> >   * Provide a brief note in the documentation connecting the new names\n> >     to the old names in case users run across the old names anywhere\n> >     (e.g. in old release notes or older versions of the documentation)\n> >   * Change the (new) --am command line flag to --apply\n> >   * Rename some enums, variables, and functions to reinforce the new\n> >     backend names for us as well.\n> >\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n>\n> Hi,\n>\n> This broke quite a few upstream users for us today when we rolled out a\n> next with this patch added on top. To shim around the post-commit hook\n> issue, we had set a system config for all our users to use\n> merge.backend=am; the machinery is pretty intolerant to a wrongly\n> configured backend name (die() rather than a warning and fallback).\n>\n> Would it make more sense to deal with an unrecognized backend by falling\n> back to the default backend, instead?\n>\n> >       if (options.type == REBASE_UNSPECIFIED) {\n> >               if (!strcmp(options.default_backend, \"merge\"))\n> > -                     imply_interactive(&options, \"--merge\");\n> > -             else if (!strcmp(options.default_backend, \"am\"))\n> > -                     options.type = REBASE_AM;\n> > +                     imply_merge(&options, \"--merge\");\n> > +             else if (!strcmp(options.default_backend, \"apply\"))\n> > +                     options.type = REBASE_APPLY;\n> >               else\n> >                       die(_(\"Unknown rebase backend: %s\"),\n> >                           options.default_backend);\n>\n> At the very least, can this die() explain that it found that string in\n> the config so the user can have a guess as to how to fix it?\n>\n> (I realize the complained code is from earlier in the series, but this\n> patch - renaming something that used to be valid without a fallback -\n> invalidated our configs, highlighting the problem for us. So I'm\n> replying here instead.)\n\nSorry for the pain.  The earlier part of the series had only ever made\nit to next, and was reverted there, and thus, in my thinking, in the\nnew cycle no one would have ever seen the intermediate state.  (Oops,\nI forgot about cases where people tried out next towards the end of\nlast cycle before it was reverted and decided to set config based upon\nit.)\n\nI'm a little worried about ignoring the setting and just picking one;\nif the setting has been marked and they set it to e.g. \"appply\" (one\ntoo many p's), then does it really make sense to just show a warning\nbut continue using the backend they didn't want, especially since they\nmay miss the warning among the rest of the output?  I'd rather go the\nroute of improving the message, perhaps:\n        _(\"Unknown rebase.backend config setting: %s\")\nWould that work for you?\n\nAlso, I thought that you and Jonathan were going to be changing the\npost-commit hook handling[1][2].  Does this mean that you've punted on\nthat, and I need to make some kind of change here to get you to switch\nover?\n\nElijah\n\n[1] https://lore.kernel.org/git/pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/\n"},{"id":"393149","messageId":"20200312175548.GC120942@google.com","threadId":"52496","inReplyTo":"CABPp-BHyNvxQZ5q=9WXXESTPmxFe4fAiE5roGeV2H+XJ_cpDmg@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-03-12T17:55:48Z","receivedAt":"2020-03-12T17:55:54Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Elijah Newren wrote:\n> On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n\n>> This broke quite a few upstream users for us today when we rolled out a\n>> next with this patch added on top. To shim around the post-commit hook\n>> issue, we had set a system config for all our users to use\n>> merge.backend=am; the machinery is pretty intolerant to a wrongly\n>> configured backend name (die() rather than a warning and fallback).\n>>\n>> Would it make more sense to deal with an unrecognized backend by falling\n>> back to the default backend, instead?\n[...]\n> Sorry for the pain.  The earlier part of the series had only ever made\n> it to next, and was reverted there, and thus, in my thinking, in the\n> new cycle no one would have ever seen the intermediate state.  (Oops,\n> I forgot about cases where people tried out next towards the end of\n> last cycle before it was reverted and decided to set config based upon\n> it.)\n\nRight, I'm mostly interested in this for the future: do we expect the\nlist of backends to only grow over time, or do we want to support\nremoving and renaming backends?  In the latter case, how can we support\npeople sharing config between multiple Git versions with their\nmerge.backend settings?\n\n> I'm a little worried about ignoring the setting and just picking one;\n> if the setting has been marked and they set it to e.g. \"appply\" (one\n> too many p's), then does it really make sense to just show a warning\n> but continue using the backend they didn't want, especially since they\n> may miss the warning among the rest of the output?  I'd rather go the\n> route of improving the message, perhaps:\n>         _(\"Unknown rebase.backend config setting: %s\")\n> Would that work for you?\n\nWhat if we support multiple merge.backend values, with semantics:\n\n- last recognized value wins\n- if no value is specified, use the default\n- if values are specified but none are recognized, error out with a\n  clear error message\n\n?\n\n> Also, I thought that you and Jonathan were going to be changing the\n> post-commit hook handling[1][2].  Does this mean that you've punted on\n> that, and I need to make some kind of change here to get you to switch\n> over?\n\nThe setting was a stopgap; our interest in this upstream is primarily\nfrom the point of view of \"we ran into this, so let's take the\nopportunity to help others that might run into similar issues in the\nfuture\".\n\nThanks,\nJonathan\n"},{"id":"393156","messageId":"CABPp-BFLwpa019Prd3nf7s4BY2jWp8utOvJD9pzHcbg66b8fWw@mail.gmail.com","threadId":"52496","inReplyTo":"20200312175548.GC120942@google.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-12T18:39:08Z","receivedAt":"2020-03-12T18:39:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> Elijah Newren wrote:\n> > On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> >> This broke quite a few upstream users for us today when we rolled out a\n> >> next with this patch added on top. To shim around the post-commit hook\n> >> issue, we had set a system config for all our users to use\n> >> merge.backend=am; the machinery is pretty intolerant to a wrongly\n> >> configured backend name (die() rather than a warning and fallback).\n> >>\n> >> Would it make more sense to deal with an unrecognized backend by falling\n> >> back to the default backend, instead?\n> [...]\n> > Sorry for the pain.  The earlier part of the series had only ever made\n> > it to next, and was reverted there, and thus, in my thinking, in the\n> > new cycle no one would have ever seen the intermediate state.  (Oops,\n> > I forgot about cases where people tried out next towards the end of\n> > last cycle before it was reverted and decided to set config based upon\n> > it.)\n>\n> Right, I'm mostly interested in this for the future: do we expect the\n> list of backends to only grow over time, or do we want to support\n> removing and renaming backends?  In the latter case, how can we support\n> people sharing config between multiple Git versions with their\n> merge.backend settings?\n\nGood questions.  We have already stated that backends might be\n\"removed\" or at least \"ignored\"; from Documentation/config/rebase.txt:\n\nrebase.backend::\n        Default backend to use for rebasing.  Possible choices are\n        'apply' or 'merge'.  In the future, if the merge backend gains\n        all remaining capabilities of the apply backend, this setting\n        may become unused.\n\nIn other words, we're already telling users that 'apply' might mean\n'merge' in future versions of git.  (Or at least that was my mental\nmodel; perhaps I should tighten up the wording to state it that way?)\n\nI hadn't thought in terms of adding backends, especially since we've\nbeen trying to remove them, though given our history[1] maybe I should\nhave.  I guess it's possible we could introduce another backend[2],\nthough we don't necessarily have to make it possible to configure\nevery backend as the default.  For example, despite the technical\nexistence of another backend right now, I did not make\n'preserve-merges' a possible setting since it is already deprecated.\n(And to go on a bit of a tangent, I think that if someone is not in\nthe middle of a rebase and tries to start one with the -p flag, we\nshould just throw a warning and pretend they passed -r.  We should\nkill preserve-merges as much as possible.  Anyway, enough of that\ntangent...)\n\n[1] By my count, we've had at least five rebase backends: apply/am,\nmerge-via-shell-out-to-merge-recursive-builtin,\nlegacy-interactive-via-shell,\ninteractive-as-builtin-and-now-called-merge, and preserve-merges.  I\nthink at one point all five co-existed.  And I wouldn't be surprised\nif I've forgotten, overlooked, or otherwise missed others.\n\n[2] Maybe I even started one with\nhttps://github.com/newren/git/blob/git-merge-2020-demo/builtin/fast-rebase.c,\nbut unless it's just too hard to refactor sequencer.c &\nbuiltin/rebase.c to make use of merge-ort (AND quit forking\nsubprocesses other than with --exec and -s AND avoid writing state\nfiles unless and until you hit a conflict), I'm thinking that dies as\nnothing more than a demo.\n\n> > I'm a little worried about ignoring the setting and just picking one;\n> > if the setting has been marked and they set it to e.g. \"appply\" (one\n> > too many p's), then does it really make sense to just show a warning\n> > but continue using the backend they didn't want, especially since they\n> > may miss the warning among the rest of the output?  I'd rather go the\n> > route of improving the message, perhaps:\n> >         _(\"Unknown rebase.backend config setting: %s\")\n> > Would that work for you?\n>\n> What if we support multiple merge.backend values, with semantics:\n>\n> - last recognized value wins\n> - if no value is specified, use the default\n> - if values are specified but none are recognized, error out with a\n>   clear error message\n>\n> ?\n\nSure, but...isn't that what we already do, other than maybe the\n'clear' part of step 3?  ('merge' and 'apply' are recognized values.\nIf no value is specified, the default of merge is used.  If values are\nspecified but none are recognized, it errors out.)  Would my suggested\nchange above fix it so that we can consider it sufficiently clear, or\ndo you want something else?\n\n> > Also, I thought that you and Jonathan were going to be changing the\n> > post-commit hook handling[1][2].  Does this mean that you've punted on\n> > that, and I need to make some kind of change here to get you to switch\n> > over?\n>\n> The setting was a stopgap; our interest in this upstream is primarily\n> from the point of view of \"we ran into this, so let's take the\n> opportunity to help others that might run into similar issues in the\n> future\".\n\nThanks for flagging it; I appreciate it.  Is my suggested rewording of\nthe error message helpful?  Have I missed or misunderstood anything\nelse you were trying to bring up?\n"},{"id":"393157","messageId":"20200312184621.GD120942@google.com","threadId":"52496","inReplyTo":"CABPp-BFLwpa019Prd3nf7s4BY2jWp8utOvJD9pzHcbg66b8fWw@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-03-12T18:46:21Z","receivedAt":"2020-03-12T18:46:27Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Elijah Newren wrote:\n> On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n\n>> What if we support multiple merge.backend values, with semantics:\n>>\n>> - last recognized value wins\n>> - if no value is specified, use the default\n>> - if values are specified but none are recognized, error out with a\n>>   clear error message\n>>\n>> ?\n>\n> Sure, but...isn't that what we already do, other than maybe the\n> 'clear' part of step 3?\n\nSorry for the lack of clarity.  I mean allowing\n\n\t[rebase]\n\t\tbackend = am\n\t\tbackend = apply\n\t\tbackend = futuristic\n\nwith behavior\n\n- on \"git\" that understands am but not apply or futuristic, use the am\n  backend\n- on \"git\" that understands apply but not am or futuristic, use the\n  apply backend\n- on \"git\" that understands apply and futuristic, use the futuristic\n  backend\n\nThat way, a single config file is usable on all three versions of Git.\n\nThanks,\nJonathan\n"},{"id":"393159","messageId":"xmqqk13pfl3m.fsf@gitster.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BHyNvxQZ5q=9WXXESTPmxFe4fAiE5roGeV2H+XJ_cpDmg@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-12T19:07:25Z","receivedAt":"2020-03-12T19:07:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> I'm a little worried about ignoring the setting and just picking one;\n\nI am more than a little worried, too.  I think erroring out is\nwarranted in this case for exactly the reason you gave here.\n\n> if the setting has been marked and they set it to e.g. \"appply\" (one\n> too many p's), then does it really make sense to just show a warning\n> but continue using the backend they didn't want, especially since they\n> may miss the warning among the rest of the output?  I'd rather go the\n> route of improving the message, perhaps:\n>         _(\"Unknown rebase.backend config setting: %s\")\n\n"},{"id":"393161","messageId":"xmqqftedfkvy.fsf@gitster.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BHyNvxQZ5q=9WXXESTPmxFe4fAiE5roGeV2H+XJ_cpDmg@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-12T19:12:01Z","receivedAt":"2020-03-12T19:12:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n>> ...\n>> This broke quite a few upstream users for us today...\n\nOne more thing I've been wondering was if we should really have said\n\"ok, the two known gotchas have been dealt with, so let's ship it\".\nIt is quite likely that users may be hit by remaining differences\nbetween the merge and apply backends that we assume to be merely\nsubtle and easy-to-work-around ones, and it may be prudent to use\nthe \"let's not change the default just yet, but ask people to test\nit in their workflow by advertising the configuration\" patch.\n"},{"id":"393162","messageId":"20200312191213.GE120942@google.com","threadId":"52496","inReplyTo":"xmqqk13pfl3m.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-03-12T19:12:13Z","receivedAt":"2020-03-12T19:12:18Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nJunio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n\n>> I'm a little worried about ignoring the setting and just picking one;\n>\n> I am more than a little worried, too.  I think erroring out is\n> warranted in this case for exactly the reason you gave here.\n\nTo avoid misunderstandings: have you read the proposal I made that aims\nto avoid that problem?\n\nThanks,\nJonathan\n\n>> if the setting has been marked and they set it to e.g. \"appply\" (one\n>> too many p's), then does it really make sense to just show a warning\n>> but continue using the backend they didn't want, especially since they\n>> may miss the warning among the rest of the output?  I'd rather go the\n>> route of improving the message, perhaps:\n>>         _(\"Unknown rebase.backend config setting: %s\")\n"},{"id":"393164","messageId":"CABPp-BGvqUEDoj6_mUAsSVeS8+h5ruCFcMTENtf5LY2XWKVj-g@mail.gmail.com","threadId":"52496","inReplyTo":"xmqqftedfkvy.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-12T19:29:45Z","receivedAt":"2020-03-12T19:30:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Mar 12, 2020 at 12:12 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n> >> ...\n> >> This broke quite a few upstream users for us today...\n>\n> One more thing I've been wondering was if we should really have said\n> \"ok, the two known gotchas have been dealt with, so let's ship it\".\n> It is quite likely that users may be hit by remaining differences\n> between the merge and apply backends that we assume to be merely\n> subtle and easy-to-work-around ones, and it may be prudent to use\n> the \"let's not change the default just yet, but ask people to test\n> it in their workflow by advertising the configuration\" patch.\n\nGood question, but there's several things to unpack here...\n\nFirst, note that this particular breakage would have occurred\nregardless of the default setting, because the problem was that they\nsetting rebase.backend to an unrecognized value, not that we used a\ndifferent backend than they were used to.\n\nSecond, for the particular case of the post-commit hook that they\nreferenced in their rationale, that item was documented near the end\nof the 2.25 cycle and mentioned in previous patchsets[1, 2] so I\nfigured this case was already considered.\n\nThose two points may obscure the issue, though; your question is still\nvalid.  I think the bigger question is whether there are other unknown\ndifferences, or even known differences that are a bigger issue than we\ncurrently realize.  That's hard to judge, and it may not be possible\nto judge until we've flipped the default.  As such, it's a judgement\ncall.  I can see the judgement call going either way.  A couple things\nto weigh in on how to go:\n   - Making rebase.backend default to 'apply' for 2.26 is certainly\nthe conservative option and may give us more feedback and time to iron\nout differences\n   - We had multiple complaints this cycle about rebase.backend=apply\nmerging things incorrectly with the only workaround being to use the\nmerge backend[3,4]\n   - The rebase-backend topic wasn't merged down to master until less\nthan a week before -rc0.  (For a variety of reasons.)  A big change\nlike this probably would have been better to merge down earlier in\nsome cycle.\n\nTo be honest, if I was maintainer, I'm not sure which direction I'd\npick.  If you feel safer switching the default backend to apply for\nthis cycle and then (re-)revert it early next cycle to the merge\nbackend, I think that's totally reasonable.\n\n\n[1] https://lore.kernel.org/git/pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/\n[3] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/\n[4] https://lore.kernel.org/git/20200108223557.GE32750@szeder.dev/\n"},{"id":"393166","messageId":"CABPp-BHSAbJzWEsPSTM5Q6MPdmu4VSuOx-=6-MJkHUovg3_1=g@mail.gmail.com","threadId":"52496","inReplyTo":"20200312184621.GD120942@google.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-12T19:31:46Z","receivedAt":"2020-03-12T19:32:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> Elijah Newren wrote:\n> > On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> >> What if we support multiple merge.backend values, with semantics:\n> >>\n> >> - last recognized value wins\n> >> - if no value is specified, use the default\n> >> - if values are specified but none are recognized, error out with a\n> >>   clear error message\n> >>\n> >> ?\n> >\n> > Sure, but...isn't that what we already do, other than maybe the\n> > 'clear' part of step 3?\n>\n> Sorry for the lack of clarity.  I mean allowing\n>\n>         [rebase]\n>                 backend = am\n>                 backend = apply\n>                 backend = futuristic\n>\n> with behavior\n>\n> - on \"git\" that understands am but not apply or futuristic, use the am\n>   backend\n> - on \"git\" that understands apply but not am or futuristic, use the\n>   apply backend\n> - on \"git\" that understands apply and futuristic, use the futuristic\n>   backend\n>\n> That way, a single config file is usable on all three versions of Git.\n\nAh, gotcha, that makes sense though we'd need to make the thing\nmulti-valued which is a bit late for 2.26.  But we could at least\nextend the logic in that way for 2.27.  Also, to further clarify, if\nthey have\n\n         [rebase]\n                 backend = misspelled\n                 backend = futuristic\n\nthen what should the error be?  A couple possibilities:\n\nfatal: Unknown rebase backend: futuristic\n\nfatal: Unknown rebase.backend config setting: futuristic\n\nfatal: Unknown rebase.backend config setting; valid values are 'merge', 'apply'.\n\nor should it be something else?\n"},{"id":"393167","messageId":"xmqq4kutfiwh.fsf@gitster.c.googlers.com","threadId":"52496","inReplyTo":"20200312184621.GD120942@google.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-12T19:54:54Z","receivedAt":"2020-03-12T19:55:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Sorry for the lack of clarity.  I mean allowing\n>\n> \t[rebase]\n> \t\tbackend = am\n> \t\tbackend = apply\n> \t\tbackend = futuristic\n>\n> with behavior\n>\n> - on \"git\" that understands am but not apply or futuristic, use the am\n>   backend\n> - on \"git\" that understands apply but not am or futuristic, use the\n>   apply backend\n> - on \"git\" that understands apply and futuristic, use the futuristic\n>   backend\n>\n> That way, a single config file is usable on all three versions of Git.\n\nIOW, make rebase.backend a multi-valued one and use it when one and\nonly one value is a recognised one?  What would an error message say\nwhen none is recognised, and when would that be issued?\n\nI like the general direction it takes, but I think it would be\nsimpler to (1) die when we see what we do not recognise and (2)\nteach the latest one that \"am\" is a synonym for \"apply\", without\neven advertising \"am\".\n\n"},{"id":"393169","messageId":"20200312203718.GA870787@coredump.intra.peff.net","threadId":"52496","inReplyTo":"CABPp-BGvqUEDoj6_mUAsSVeS8+h5ruCFcMTENtf5LY2XWKVj-g@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-03-12T20:37:18Z","receivedAt":"2020-03-12T20:37:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Mar 12, 2020 at 12:29:45PM -0700, Elijah Newren wrote:\n\n> First, note that this particular breakage would have occurred\n> regardless of the default setting, because the problem was that they\n> setting rebase.backend to an unrecognized value, not that we used a\n> different backend than they were used to.\n\nIf I understand correctly, it was also a setting that never worked in\nany released version of Git. It was magic that was only ever in 'next'.\n\nAs annoying as it is to experience breakage, I'm not _too_ sympathetic\nto this case, because that is part of the cost of running the bleeding\nedge of 'next' or even 'master'. I.e., I think we have to make a cutoff\n_somewhere_ to say \"this is something that made it to the general\npublic, and therefore we can't break backwards compatibility\" to keep\nour sanity during development. And it seems like tagged releases are a\npretty good cutoff to me.\n\nThough in this particular case, I don't mind too much just leaving \"am\"\nas an alias for \"apply\" (it was actually the first thing I tried when\nwriting my earlier emails, but I'm probably not a representative user\nthere). Putting that in a release, though, may mean supporting it\nforever. :)\n\n>    - We had multiple complaints this cycle about rebase.backend=apply\n> merging things incorrectly with the only workaround being to use the\n> merge backend[3,4]\n>    - The rebase-backend topic wasn't merged down to master until less\n> than a week before -rc0.  (For a variety of reasons.)  A big change\n> like this probably would have been better to merge down earlier in\n> some cycle.\n\nIt did feel a bit quick to me, hitting near the end of the cycle. We've\nhad the apply backend as the default for a decade, so even if there are\nproblems with it, they're known issues. So I don't think there's a\nparticular hurry. I'm not entirely convinced that cooking it longer\nduring the next cycle will turn up a lot of new data (I did find a few\nissues, but the real test is the long-tail of weird use cases that we\nwon't see until an actual release). But it probably doesn't hurt much to\ntake it slow; it just delays a few bug-fixes (which people can still get\nby setting a config option).\n\nI guess like your email I'm going back and forth between the two\noptions. I think that means it probably doesn't matter _too_ much either\nway.\n\n-Peff\n"},{"id":"393175","messageId":"xmqqo8t1e01r.fsf@gitster.c.googlers.com","threadId":"52496","inReplyTo":"20200312203718.GA870787@coredump.intra.peff.net","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-12T21:27:28Z","receivedAt":"2020-03-12T21:27:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Though in this particular case, I don't mind too much just leaving \"am\"\n> as an alias for \"apply\" (it was actually the first thing I tried when\n> writing my earlier emails, but I'm probably not a representative user\n> there). Putting that in a release, though, may mean supporting it\n> forever. :)\n\nYeah, I agree that what used to exist only in 'next' and we chose to\nremove it before it its 'master', does not deserve to be supported\nforever.  So let's scrap the \"am is taken as a synonym for apply\".\nIt would not help the old version taken from 'next' grok a new\nconfiguration file that uses \"apply\" anyway ;-)\n\n> I guess like your email I'm going back and forth between the two\n> options. I think that means it probably doesn't matter _too_ much either\n> way.\n\nOK, let's avoid screwing it up even further by doing no more damage\nthan merging just the three fix-ups we discussed recently.  \n\nThe fact that jrnieder runs 'next'+patches for his $DAYJOB users\nmakes me hope that there may be other organizations that do the\nsame, and cooking in 'next' would mean somthing, though.\n\nWe may want to think of a way to strongly encourage those who are in\ncharge of choosing and maintaining the versions of Git that is used\nin their organization, whose operation depends on the healthy future\nversions of Git, to run 'next' or at least 'master', to stay ahead\nof the released versions.  Some education and advocacy is needed?\n\n"},{"id":"393181","messageId":"CABPp-BGQ0anV7P19PF6NgosF4psQ=TQp=8B6dt2U+ENE2Ubjew@mail.gmail.com","threadId":"52496","inReplyTo":"xmqqo8t1e01r.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-12T22:06:14Z","receivedAt":"2020-03-12T22:06:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Mar 12, 2020 at 2:27 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Jeff King <peff@peff.net> writes:\n>\n> > Though in this particular case, I don't mind too much just leaving \"am\"\n> > as an alias for \"apply\" (it was actually the first thing I tried when\n> > writing my earlier emails, but I'm probably not a representative user\n> > there). Putting that in a release, though, may mean supporting it\n> > forever. :)\n>\n> Yeah, I agree that what used to exist only in 'next' and we chose to\n> remove it before it its 'master', does not deserve to be supported\n> forever.  So let's scrap the \"am is taken as a synonym for apply\".\n> It would not help the old version taken from 'next' grok a new\n> configuration file that uses \"apply\" anyway ;-)\n>\n> > I guess like your email I'm going back and forth between the two\n> > options. I think that means it probably doesn't matter _too_ much either\n> > way.\n>\n> OK, let's avoid screwing it up even further by doing no more damage\n> than merging just the three fix-ups we discussed recently.\n>\n> The fact that jrnieder runs 'next'+patches for his $DAYJOB users\n> makes me hope that there may be other organizations that do the\n> same, and cooking in 'next' would mean somthing, though.\n>\n> We may want to think of a way to strongly encourage those who are in\n> charge of choosing and maintaining the versions of Git that is used\n> in their organization, whose operation depends on the healthy future\n> versions of Git, to run 'next' or at least 'master', to stay ahead\n> of the released versions.  Some education and advocacy is needed?\n\nI agree, it's super cool that Emily and Jonathan distribute 'next' to\ntheir users at Google and provide us lots of early feedback.  I wish\nwe had something similar; currently, the only control I have is\nrequesting that some documentation file that includes a recommended\nminimum git version be bumped to something newer, and I usually need\nto provide a reason (I can't just say, \"It's the newest release\").\nOther than that, developers install whatever version of git they like,\npossibly unaware of what's in that documentation file.\n\nIn fact, not having a way to control git versions is leading to\ndiscussions of spending my time to not only fix issues in git (e.g.\nthe read-tree -mu HEAD problems with sparse-checkout) but also writing\nsome separate program that does that piece independent of git so that\nwe can work with a variety of git versions.  Such a pain...does anyone\nknow how to educate me to advocate to the company that we come up with\nsome more controlled git version?\n"},{"id":"393185","messageId":"20200312233040.GG120942@google.com","threadId":"52496","inReplyTo":"xmqqo8t1e01r.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-03-12T23:30:40Z","receivedAt":"2020-03-12T23:30:46Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n\n>           So let's scrap the \"am is taken as a synonym for apply\".\n> It would not help the old version taken from 'next' grok a new\n> configuration file that uses \"apply\" anyway ;-)\n\nAgreed.  For 2.26.0, I'm happy with either\n\n- what we currently have, where it defaults to 'merge', or\n- the more conservative approach where it supports 'merge' but defaults\n  to 'apply'\n\nAccepting multiple values for forward compatibility is an optional\ncherry on top: I would like us to eventually get there, but I don't\nmind if it doesn't make 2.26.0, and it's probably better to give a\nchange like that some cooking time.  (Although I won't complain if it\ndoes make 2.26.0. ;-))\n\n[...]\n> We may want to think of a way to strongly encourage those who are in\n> charge of choosing and maintaining the versions of Git that is used\n> in their organization, whose operation depends on the healthy future\n> versions of Git, to run 'next' or at least 'master', to stay ahead\n> of the released versions.  Some education and advocacy is needed?\n\nIt's possible we should write up some best practices somewhere in\nDocumentation/ about how to make running \"next\" go smoothly:\n\n- have a responsive infrastructure team.  Pay attention to changes\n  landing upstream and have the infra team test before the rest of\n  your user population :)\n\n- if you have a large user population, use gradual rollouts so that a\n  subset of users can find problematic changes before they affect\n  everyone\n\n- fast rollbacks\n\n- telemetry to catch regressions (in latency, for example) when people\n  are too shy to report them\n\nWe can also advertise places, such as Debian experimental, that people\ncan get snapshots without maintaining them themselves.\n\nThanks,\nJonathan\n"},{"id":"393188","messageId":"xmqqfteddsrj.fsf@gitster.c.googlers.com","threadId":"52496","inReplyTo":"CABPp-BGQ0anV7P19PF6NgosF4psQ=TQp=8B6dt2U+ENE2Ubjew@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-13T00:04:48Z","receivedAt":"2020-03-13T00:04:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> We may want to think of a way to strongly encourage those who are in\n>> charge of choosing and maintaining the versions of Git that is used\n>> in their organization, whose operation depends on the healthy future\n>> versions of Git, to run 'next' or at least 'master', to stay ahead\n>> of the released versions.  Some education and advocacy is needed?\n>\n> I agree, it's super cool that Emily and Jonathan distribute 'next' to\n> their users at Google and provide us lots of early feedback.  I wish\n> we had something similar; currently, the only control I have is\n> requesting that some documentation file that includes a recommended\n> minimum git version be bumped to something newer, and I usually need\n> to provide a reason (I can't just say, \"It's the newest release\").\n\nOf course, within such an environment, \"It's to help make sure that\nthe upcoming release won't be broken for us\" is hard to phrase\nin a palatable way for the management :-<.\n"},{"id":"393340","messageId":"20200317025808.GB31380@google.com","threadId":"52496","inReplyTo":"CABPp-BHSAbJzWEsPSTM5Q6MPdmu4VSuOx-=6-MJkHUovg3_1=g@mail.gmail.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2020-03-17T02:58:08Z","receivedAt":"2020-03-17T02:58:17Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nElijah Newren wrote:\n> On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n\n>> Sorry for the lack of clarity.  I mean allowing\n>>\n>>         [rebase]\n>>                 backend = am\n>>                 backend = apply\n>>                 backend = futuristic\n>>\n>> with behavior\n>>\n>> - on \"git\" that understands am but not apply or futuristic, use the am\n>>   backend\n>> - on \"git\" that understands apply but not am or futuristic, use the\n>>   apply backend\n>> - on \"git\" that understands apply and futuristic, use the futuristic\n>>   backend\n>>\n>> That way, a single config file is usable on all three versions of Git.\n>\n> Ah, gotcha, that makes sense though we'd need to make the thing\n> multi-valued which is a bit late for 2.26.  But we could at least\n> extend the logic in that way for 2.27.\n\nHere's a patch implementing that.  I'm not convinced it's worth the\ncomplexity, mostly because I'm not convinced that rebase is going to\nhave to select between additional new backends in the future.  But if\nyou think it will, then I think this would be a reasonable thing to do\n(maybe even without the documentation part of the patch).\n\nThoughts?\n\nThanks,\nJonathan\n\n-- >8 --\nSubject: rebase: allow specifying unrecognized rebase.backend with a fallback\n\nIn 8295ed690bf (rebase: make the backend configurable via config\nsetting, 2020-02-15), Git learned a new rebase.backend setting that\ncan be used to specify which implementation should be used for\nnon-interactive rebases: \"am\" (now called \"apply\"), which uses \"git\nam\", or \"merge\", which uses the three-way merge machinery.\n\nMost likely those are the only two backends that rebase will ever need\nto learn, so this level of configurability would be sufficient.  At\nsome point the \"apply\" backend would be retired, and the setting would\nbe removed altogether.\n\nSuppose, though, that rebase learns another backend --- e.g. \"faster\".\nIn that case, a user might set configuration to request it:\n\n\t[rebase]\n\t\tbackend = faster\n\nIf their configuration is shared between multiple versions of Git\n(think \"home directory on NFS shared between machines\"), this would\nproduce errors when read by older versions of Git:\n\n\tfatal: Unknown rebase backend: faster\n\nOn the other hand, if we ignore unrecognized rebase backend settings,\nthen Git would fail to realize that\n\n\t[rebase]\n\t\tbackend = appply\n\nis a typo, producing a confusing user experience.  Let's do something\nin between: when a rebase backend setting is unrecognized, fall back\nto the last earlier recognized value, but if no value was recognized,\nprint an error message allowing the user to catch their typo.\n\nReported-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n Documentation/config/rebase.txt |  5 ++\n builtin/rebase.c                | 52 +++++++++++++++---\n t/t3435-rebase-backend.sh       | 97 +++++++++++++++++++++++++++++++++\n 3 files changed, 146 insertions(+), 8 deletions(-)\n create mode 100755 t/t3435-rebase-backend.sh\n\ndiff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\nindex 7f7a07d22f8..c92adbdcc69 100644\n--- a/Documentation/config/rebase.txt\n+++ b/Documentation/config/rebase.txt\n@@ -10,6 +10,11 @@ rebase.backend::\n \t'apply' or 'merge'.  In the future, if the merge backend gains\n \tall remaining capabilities of the apply backend, this setting\n \tmay become unused.\n++\n+If set multiple times, the last value corresponding to a recognized\n+backend is used. This is for forward compatibility, as it allows\n+specifying a rebase backend that Git does not know about yet along\n+with a backend known today as a fallback.\n \n rebase.stat::\n \tWhether to show a diffstat of what changed upstream since the last\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ffa467aad52..5b0fab9741f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -56,10 +56,18 @@ enum empty_type {\n \tEMPTY_ASK\n };\n \n+enum rebase_backend {\n+\tBACKEND_UNSPECIFIED = 0,\n+\tBACKEND_UNRECOGNIZED,\n+\tBACKEND_APPLY,\n+\tBACKEND_MERGE,\n+};\n+\n struct rebase_options {\n \tenum rebase_type type;\n \tenum empty_type empty;\n-\tconst char *default_backend;\n+\tenum rebase_backend configured_backend;\n+\tconst char *last_specified_backend;\n \tconst char *state_dir;\n \tstruct commit *upstream;\n \tconst char *upstream_name;\n@@ -100,7 +108,6 @@ struct rebase_options {\n #define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = ARGV_ARRAY_INIT,\t\t\\\n \t\t.git_format_patch_opt = STRBUF_INIT\t\\\n@@ -1224,6 +1231,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n \treturn status ? -1 : 0;\n }\n \n+static enum rebase_backend parse_rebase_backend(const char *value)\n+{\n+\tif (!strcmp(value, \"apply\"))\n+\t\treturn BACKEND_APPLY;\n+\tif (!strcmp(value, \"merge\"))\n+\t\treturn BACKEND_MERGE;\n+\treturn BACKEND_UNRECOGNIZED;\n+}\n+\n static int rebase_config(const char *var, const char *value, void *data)\n {\n \tstruct rebase_options *opts = data;\n@@ -1264,7 +1280,18 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n-\t\treturn git_config_string(&opts->default_backend, var, value);\n+\t\tenum rebase_backend val;\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tval = parse_rebase_backend(value);\n+\t\tif (opts->configured_backend == BACKEND_UNSPECIFIED)\n+\t\t\topts->configured_backend = val;\n+\t\telse if (val == BACKEND_UNRECOGNIZED)\n+\t\t\t; /* Unrecognized rebase backend. Ignore it. */\n+\t\telse\n+\t\t\topts->configured_backend = val;\n+\t\topts->last_specified_backend = value;\n+\t\treturn 0;\n \t}\n \n \treturn git_default_config(var, value, data);\n@@ -1903,14 +1930,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\tif (options.configured_backend == BACKEND_UNRECOGNIZED)\n+\t\tdie(_(\"unknown rebase backend: %s\"),\n+\t\t    options.last_specified_backend);\n+\n \tif (options.type == REBASE_UNSPECIFIED) {\n-\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\tswitch (options.configured_backend) {\n+\t\tcase BACKEND_UNSPECIFIED:\n+\t\tcase BACKEND_MERGE:\n \t\t\timply_merge(&options, \"--merge\");\n-\t\telse if (!strcmp(options.default_backend, \"apply\"))\n+\t\t\tbreak;\n+\t\tcase BACKEND_APPLY:\n \t\t\toptions.type = REBASE_APPLY;\n-\t\telse\n-\t\t\tdie(_(\"Unknown rebase backend: %s\"),\n-\t\t\t    options.default_backend);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tBUG(\"unexpected backend %d\",\n+\t\t\t    (int) options.configured_backend);\n+\t\t}\n \t}\n \n \tswitch (options.type) {\ndiff --git a/t/t3435-rebase-backend.sh b/t/t3435-rebase-backend.sh\nnew file mode 100755\nindex 00000000000..8b9ba6f1894\n--- /dev/null\n+++ b/t/t3435-rebase-backend.sh\n@@ -0,0 +1,97 @@\n+#!/bin/sh\n+\n+test_description='rebase.backend tests\n+\n+Checks of config parsing for the [rebase] backend setting.  We detect\n+which backend was used by checking which directory was created to hold\n+state.'\n+\n+. ./test-lib.sh\n+\n+# usage: test_backend_choice <expectation> <command>\n+#\n+# Tests that the chosen backend for rebase command <command>\n+# is <expectation> (\"merge\" or \"apply\").\n+test_backend_choice () {\n+\texpect=$1 &&\n+\tshift &&\n+\n+\ttest_must_fail git \"$@\" master topic &&\n+\tcase $expect in\n+\tapply)\n+\t\ttest_path_is_dir .git/rebase-apply &&\n+\t\ttest_path_is_missing .git/rebase-merge\n+\t\t;;\n+\tmerge)\n+\t\ttest_path_is_dir .git/rebase-merge &&\n+\t\ttest_path_is_missing .git/rebase-apply\n+\t\t;;\n+\t*)\n+\t\terror \"unrecognized expectation $expect\"\n+\tesac\n+}\n+\n+test_expect_success 'setup' '\n+\ttest_commit base &&\n+\ttest_commit sidea conflict.txt myway &&\n+\tgit checkout -b topic base &&\n+\ttest_commit sideb conflict.txt thehighway\n+'\n+\n+test_expect_success '--apply uses apply backend' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice apply rebase --apply\n+'\n+\n+test_expect_success '--merge uses merge backend' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice merge rebase --merge\n+'\n+\n+test_expect_success 'default to merge backend' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice merge rebase\n+'\n+\n+test_expect_success 'config overrides default' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice apply -c rebase.backend=apply rebase\n+'\n+\n+test_expect_success 'option overrides config' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice merge -c rebase.backend=apply rebase --merge\n+'\n+\n+test_expect_success 'last config value wins' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice merge \\\n+\t\t-c rebase.backend=apply \\\n+\t\t-c rebase.backend=merge \\\n+\t\trebase\n+'\n+\n+test_expect_success 'last config value wins' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice merge \\\n+\t\t-c rebase.backend=apply \\\n+\t\t-c rebase.backend=merge \\\n+\t\trebase\n+'\n+\n+test_expect_success 'misspelled backend without fallback is diagnosed' '\n+\ttest_must_fail \\\n+\t\tgit -c rebase.backend=appply rebase master topic 2>message &&\n+\ttest_i18ngrep \"unknown rebase backend\" message &&\n+\tgrep appply message\n+'\n+\n+test_expect_success 'forward compatibility by skipping unrecognized values' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\ttest_backend_choice apply \\\n+\t\t-c rebase.backend=apply \\\n+\t\t-c rebase.backend=futuristic \\\n+\t\trebase\n+'\n+\n+test_done\n-- \n2.25.1.481.gfbce0eb801\n\n"},{"id":"393343","messageId":"CABPp-BGvatgaTkorJEErvYbVvci2b5AQgCH6Q0z81426tKZ04A@mail.gmail.com","threadId":"52496","inReplyTo":"20200317025808.GB31380@google.com","subject":"Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-03-17T04:45:10Z","receivedAt":"2020-03-17T04:45:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Jonathan,\n\nOn Mon, Mar 16, 2020 at 7:58 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> Hi,\n>\n> Elijah Newren wrote:\n> > On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:\n>\n> >> Sorry for the lack of clarity.  I mean allowing\n> >>\n> >>         [rebase]\n> >>                 backend = am\n> >>                 backend = apply\n> >>                 backend = futuristic\n> >>\n> >> with behavior\n> >>\n> >> - on \"git\" that understands am but not apply or futuristic, use the am\n> >>   backend\n> >> - on \"git\" that understands apply but not am or futuristic, use the\n> >>   apply backend\n> >> - on \"git\" that understands apply and futuristic, use the futuristic\n> >>   backend\n> >>\n> >> That way, a single config file is usable on all three versions of Git.\n> >\n> > Ah, gotcha, that makes sense though we'd need to make the thing\n> > multi-valued which is a bit late for 2.26.  But we could at least\n> > extend the logic in that way for 2.27.\n>\n> Here's a patch implementing that.  I'm not convinced it's worth the\n> complexity, mostly because I'm not convinced that rebase is going to\n> have to select between additional new backends in the future.  But if\n> you think it will, then I think this would be a reasonable thing to do\n> (maybe even without the documentation part of the patch).\n>\n> Thoughts?\n\nThanks for investigating what's involved.  If there will be new rebase\nbackends, then this does look like nice future proofing to me.  As for\nwhether there will be...\n\nPersonally, I would rather decrease the number of backends than\nincrease, and if it was up to just me, I'd like to drive the number of\nbackends to one and then keep it there.  But it feels hard to know for\nsure.\n\n> Thanks,\n> Jonathan\n>\n> -- >8 --\n> Subject: rebase: allow specifying unrecognized rebase.backend with a fallback\n>\n> In 8295ed690bf (rebase: make the backend configurable via config\n> setting, 2020-02-15), Git learned a new rebase.backend setting that\n> can be used to specify which implementation should be used for\n> non-interactive rebases: \"am\" (now called \"apply\"), which uses \"git\n> am\", or \"merge\", which uses the three-way merge machinery.\n>\n> Most likely those are the only two backends that rebase will ever need\n> to learn, so this level of configurability would be sufficient.  At\n> some point the \"apply\" backend would be retired, and the setting would\n> be removed altogether.\n>\n> Suppose, though, that rebase learns another backend --- e.g. \"faster\".\n> In that case, a user might set configuration to request it:\n>\n>         [rebase]\n>                 backend = faster\n>\n> If their configuration is shared between multiple versions of Git\n> (think \"home directory on NFS shared between machines\"), this would\n> produce errors when read by older versions of Git:\n>\n>         fatal: Unknown rebase backend: faster\n>\n> On the other hand, if we ignore unrecognized rebase backend settings,\n> then Git would fail to realize that\n>\n>         [rebase]\n>                 backend = appply\n>\n> is a typo, producing a confusing user experience.  Let's do something\n> in between: when a rebase backend setting is unrecognized, fall back\n> to the last earlier recognized value, but if no value was recognized,\n> print an error message allowing the user to catch their typo.\n>\n> Reported-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n>  Documentation/config/rebase.txt |  5 ++\n>  builtin/rebase.c                | 52 +++++++++++++++---\n>  t/t3435-rebase-backend.sh       | 97 +++++++++++++++++++++++++++++++++\n>  3 files changed, 146 insertions(+), 8 deletions(-)\n>  create mode 100755 t/t3435-rebase-backend.sh\n>\n> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt\n> index 7f7a07d22f8..c92adbdcc69 100644\n> --- a/Documentation/config/rebase.txt\n> +++ b/Documentation/config/rebase.txt\n> @@ -10,6 +10,11 @@ rebase.backend::\n>         'apply' or 'merge'.  In the future, if the merge backend gains\n>         all remaining capabilities of the apply backend, this setting\n>         may become unused.\n> ++\n> +If set multiple times, the last value corresponding to a recognized\n> +backend is used. This is for forward compatibility, as it allows\n> +specifying a rebase backend that Git does not know about yet along\n> +with a backend known today as a fallback.\n>\n>  rebase.stat::\n>         Whether to show a diffstat of what changed upstream since the last\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index ffa467aad52..5b0fab9741f 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -56,10 +56,18 @@ enum empty_type {\n>         EMPTY_ASK\n>  };\n>\n> +enum rebase_backend {\n> +       BACKEND_UNSPECIFIED = 0,\n> +       BACKEND_UNRECOGNIZED,\n> +       BACKEND_APPLY,\n> +       BACKEND_MERGE,\n> +};\n> +\n>  struct rebase_options {\n>         enum rebase_type type;\n>         enum empty_type empty;\n> -       const char *default_backend;\n> +       enum rebase_backend configured_backend;\n> +       const char *last_specified_backend;\n>         const char *state_dir;\n>         struct commit *upstream;\n>         const char *upstream_name;\n> @@ -100,7 +108,6 @@ struct rebase_options {\n>  #define REBASE_OPTIONS_INIT {                          \\\n>                 .type = REBASE_UNSPECIFIED,             \\\n>                 .empty = EMPTY_UNSPECIFIED,             \\\n> -               .default_backend = \"merge\",             \\\n>                 .flags = REBASE_NO_QUIET,               \\\n>                 .git_am_opts = ARGV_ARRAY_INIT,         \\\n>                 .git_format_patch_opt = STRBUF_INIT     \\\n> @@ -1224,6 +1231,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)\n>         return status ? -1 : 0;\n>  }\n>\n> +static enum rebase_backend parse_rebase_backend(const char *value)\n> +{\n> +       if (!strcmp(value, \"apply\"))\n> +               return BACKEND_APPLY;\n> +       if (!strcmp(value, \"merge\"))\n> +               return BACKEND_MERGE;\n> +       return BACKEND_UNRECOGNIZED;\n> +}\n> +\n>  static int rebase_config(const char *var, const char *value, void *data)\n>  {\n>         struct rebase_options *opts = data;\n> @@ -1264,7 +1280,18 @@ static int rebase_config(const char *var, const char *value, void *data)\n>         }\n>\n>         if (!strcmp(var, \"rebase.backend\")) {\n> -               return git_config_string(&opts->default_backend, var, value);\n> +               enum rebase_backend val;\n> +               if (!value)\n> +                       return config_error_nonbool(var);\n> +               val = parse_rebase_backend(value);\n> +               if (opts->configured_backend == BACKEND_UNSPECIFIED)\n> +                       opts->configured_backend = val;\n> +               else if (val == BACKEND_UNRECOGNIZED)\n> +                       ; /* Unrecognized rebase backend. Ignore it. */\n> +               else\n> +                       opts->configured_backend = val;\n> +               opts->last_specified_backend = value;\n> +               return 0;\n>         }\n>\n>         return git_default_config(var, value, data);\n> @@ -1903,14 +1930,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>                 }\n>         }\n>\n> +       if (options.configured_backend == BACKEND_UNRECOGNIZED)\n> +               die(_(\"unknown rebase backend: %s\"),\n> +                   options.last_specified_backend);\n> +\n>         if (options.type == REBASE_UNSPECIFIED) {\n> -               if (!strcmp(options.default_backend, \"merge\"))\n> +               switch (options.configured_backend) {\n> +               case BACKEND_UNSPECIFIED:\n> +               case BACKEND_MERGE:\n>                         imply_merge(&options, \"--merge\");\n> -               else if (!strcmp(options.default_backend, \"apply\"))\n> +                       break;\n> +               case BACKEND_APPLY:\n>                         options.type = REBASE_APPLY;\n> -               else\n> -                       die(_(\"Unknown rebase backend: %s\"),\n> -                           options.default_backend);\n> +                       break;\n> +               default:\n> +                       BUG(\"unexpected backend %d\",\n> +                           (int) options.configured_backend);\n> +               }\n>         }\n>\n>         switch (options.type) {\n> diff --git a/t/t3435-rebase-backend.sh b/t/t3435-rebase-backend.sh\n> new file mode 100755\n> index 00000000000..8b9ba6f1894\n> --- /dev/null\n> +++ b/t/t3435-rebase-backend.sh\n> @@ -0,0 +1,97 @@\n> +#!/bin/sh\n> +\n> +test_description='rebase.backend tests\n> +\n> +Checks of config parsing for the [rebase] backend setting.  We detect\n> +which backend was used by checking which directory was created to hold\n> +state.'\n> +\n> +. ./test-lib.sh\n> +\n> +# usage: test_backend_choice <expectation> <command>\n> +#\n> +# Tests that the chosen backend for rebase command <command>\n> +# is <expectation> (\"merge\" or \"apply\").\n> +test_backend_choice () {\n> +       expect=$1 &&\n> +       shift &&\n> +\n> +       test_must_fail git \"$@\" master topic &&\n> +       case $expect in\n> +       apply)\n> +               test_path_is_dir .git/rebase-apply &&\n> +               test_path_is_missing .git/rebase-merge\n> +               ;;\n> +       merge)\n> +               test_path_is_dir .git/rebase-merge &&\n> +               test_path_is_missing .git/rebase-apply\n> +               ;;\n> +       *)\n> +               error \"unrecognized expectation $expect\"\n> +       esac\n> +}\n> +\n> +test_expect_success 'setup' '\n> +       test_commit base &&\n> +       test_commit sidea conflict.txt myway &&\n> +       git checkout -b topic base &&\n> +       test_commit sideb conflict.txt thehighway\n> +'\n> +\n> +test_expect_success '--apply uses apply backend' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice apply rebase --apply\n> +'\n> +\n> +test_expect_success '--merge uses merge backend' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice merge rebase --merge\n> +'\n> +\n> +test_expect_success 'default to merge backend' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice merge rebase\n> +'\n> +\n> +test_expect_success 'config overrides default' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice apply -c rebase.backend=apply rebase\n> +'\n> +\n> +test_expect_success 'option overrides config' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice merge -c rebase.backend=apply rebase --merge\n> +'\n> +\n> +test_expect_success 'last config value wins' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice merge \\\n> +               -c rebase.backend=apply \\\n> +               -c rebase.backend=merge \\\n> +               rebase\n> +'\n> +\n> +test_expect_success 'last config value wins' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice merge \\\n> +               -c rebase.backend=apply \\\n> +               -c rebase.backend=merge \\\n> +               rebase\n> +'\n\nUm, copy-and-paste-and-forget-to-edit?  This test is identical to the\none above it and thus is not useful; I think you meant to flip the\norder of config options and flip the result (and maybe give a slightly\ndifferent test name to it)?\n\n> +\n> +test_expect_success 'misspelled backend without fallback is diagnosed' '\n> +       test_must_fail \\\n> +               git -c rebase.backend=appply rebase master topic 2>message &&\n> +       test_i18ngrep \"unknown rebase backend\" message &&\n> +       grep appply message\n> +'\n> +\n> +test_expect_success 'forward compatibility by skipping unrecognized values' '\n> +       test_when_finished \"git rebase --abort\" &&\n> +       test_backend_choice apply \\\n> +               -c rebase.backend=apply \\\n> +               -c rebase.backend=futuristic \\\n> +               rebase\n> +'\n> +\n> +test_done\n\nDidn't spot anything other than that one test issue in looking over things.\n"}]}