git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/15] rebase: make the default backend configurable
@ 2019-12-20 17:09 Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
                   ` (16 more replies)
  0 siblings, 17 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano

This series does a lot of work around making the default rebase backend
configurable:

 * provide configurability of handling empty commits and iron out
   differences between backends
 * increase/fix capabilities of the merge/interactive backend to make it
   workable as the default
 * document the remaining differences in backends more thoroughly
 * add an --am option for explicitly requesting the am-backend
 * extend merge/interactive backend testing to put it on par with the am
   backend
 * add a 'rebase.backend' config option and:
 * switch the rebase.backend default value from 'am' to 'merge'.

Areas I'd like reviewers to focus, in priority order:

 * Patch 15 (Are we ready to switch the default backend? Or should we leave
   this patch out for now? Junio suggested we may be ready or at least are
   close[1])
 * Patch 1 (Does my empty handling make sense? Do others agree I fixed 
   --keep-empty, or do they view it as breaking it?)
 * Patch 8 (Do the updates to the documentation of behavioral differences
   make sense? Is it too long?) * Patch 11 (okay to change the
      git-completion shell prompt slightly, especially in light of patches
      15 & 16? We did a prompt change previously when we merged the merge
      backend with the interactive one, so I assume so, but just want to
      make sure people have a chance to chime in.)
   
   

If it's too soon to switch from the 'am' to 'merge' backend, we can just
drop the last patch and I'll resubmit it later. I generated this series
mostly through switching the default first and then watching what broke, but
moved the patch to the end to make it easy to drop.

Briefly, reasons for switching the default backend boil down to the fact
that the am-backend drops information and thus limits what it can do. This
manifests in different ways:

 * lack of tree information that would allow us to warn users that new files
   in old directories might want to move along with the other files that
   were renamed with those directories[1]
 * incorrect application of patches in the presence of non-unique context
   lines[2], which could be avoided with access to the original files
   involved.
 * less information available to annotate conflict markers (since am creates
   fake ancestors and commits on top of them, and doesn't have access to the
   original commits)

[1] https://lore.kernel.org/git/xmqqa78d2qmk.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[3] 
https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/

Elijah Newren (15):
  rebase: extend the options for handling of empty commits
  t3406: simplify an already simple test
  rebase, sequencer: remove the broken GIT_QUIET handling
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase: fix handling of restrict_revision
  t3432: make these tests work with either am or merge backends
  rebase: allow more types of rebases to fast-forward
  git-rebase.txt: add more details about behavioral differences of
    backends
  rebase: move incompatibility checks between backend options a bit
    earlier
  rebase: add an --am option
  contrib: change the prompt for am-based rebases
  rebase tests: mark tests specific to the am-backend with --am
  rebase tests: repeat some tests using the merge backend instead of am
  rebase: make the backend configurable via config setting
  rebase: change the default backend from "am" to "merge"

 Documentation/config/rebase.txt         |   8 ++
 Documentation/git-rebase.txt            | 150 ++++++++++++++++----
 builtin/rebase.c                        | 181 +++++++++++++++++++-----
 contrib/completion/git-prompt.sh        |   2 +-
 rebase-interactive.c                    |   4 +-
 rebase-interactive.h                    |   2 +-
 sequencer.c                             |  80 ++++++++---
 sequencer.h                             |   6 +-
 t/t3400-rebase.sh                       |  36 ++++-
 t/t3401-rebase-and-am-rename.sh         |   4 +-
 t/t3404-rebase-interactive.sh           |   2 +-
 t/t3406-rebase-message.sh               |  19 ++-
 t/t3407-rebase-abort.sh                 |   6 +-
 t/t3420-rebase-autostash.sh             |   2 +-
 t/t3421-rebase-topology-linear.sh       |   4 +-
 t/t3424-rebase-empty.sh                 |  89 ++++++++++++
 t/t3425-rebase-topology-merges.sh       |   8 +-
 t/t3427-rebase-subtree.sh               |  16 ++-
 t/t3432-rebase-fast-forward.sh          |  59 ++++----
 t/t3433-rebase-options-compatibility.sh |  13 +-
 t/t5407-post-rewrite-hook.sh            |  12 +-
 t/t5520-pull.sh                         |  27 +++-
 t/t6047-diff3-conflict-markers.sh       |  13 +-
 t/t7512-status-help.sh                  |  12 +-
 t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-
 t/t9903-bash-prompt.sh                  |   6 +-
 26 files changed, 582 insertions(+), 182 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh


base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1
Pull-Request: https://github.com/git/git/pull/679
-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH 01/15] rebase: extend the options for handling of empty commits
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 21:29   ` Junio C Hamano
  2019-12-20 17:09 ` [PATCH 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Extend the interactive machinery with the ability to handle the full
spread of options for how to handle commits that either start or become
empty (by "become empty" I mean the changes in a commit are a subset of
changes that exist upstream, so the net effect of applying the commit is
no changes).  Introduce a new command line flag for selecting the
desired behavior:
    --empty={drop,keep,ask}
with the definitions:
    drop: drop empty commits
    keep: keep empty commits
    ask:  provide the user a chance to interact and pick what to do with
          empty commits on a case-by-case basis

Note that traditionally, am-based rebases have always dropped commits
that either started or became empty, while interactive-based rebases
have defaulted to ask (and provided an option to keep commits that
started empty).  This difference made sense since users of an am-based
rebase just wanted to quickly batch apply a sequence of commits, while
users editing a todo list will likely want the chance to interact and
handle unusual cases on a case-by-case basis.  However, not all rebases
using the interactive machinery are explicitly interactive anymore.  In
particular --merge was always meant to behave more like --am: just
rebase a batch of commits without popping up a todo list.

If the --empty flag is not specified, pick defaults as follows:
    explicitly interactive: ask
    --exec: keep (exec is about checking existing commits, and often
                  used without actually changing the base.  Thus the
                  expectation is that the user doesn't necessarily want
                  anything to change; they just want to test).
    otherwise: drop

Also, this commit makes --keep-empty just imply --empty=keep, and hides
it from help so that we aren't confusing users with different ways to do
the same thing.  (I could have added a --drop-empty flag, but then that
invites users to specify both --keep-empty and --drop-empty and we have
to add sanity checking around that; it seems cleaner to have a single
multi-valued option.)  This actually fixes --keep-empty too; previously,
it only meant to sometimes keep empty commits, in particular commits
which started empty would be kept.  But it would still error out and ask
the user what to do with commits that became empty.  Now it keeps empty
commits, as instructed.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt      | 35 ++++++------
 builtin/rebase.c                  | 87 +++++++++++++++++++++++++++---
 rebase-interactive.c              |  4 +-
 rebase-interactive.h              |  2 +-
 sequencer.c                       | 74 +++++++++++++++++++------
 sequencer.h                       |  6 ++-
 t/t3421-rebase-topology-linear.sh |  4 +-
 t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++
 t/t3427-rebase-subtree.sh         | 16 +++---
 9 files changed, 267 insertions(+), 50 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d0e2d27cc..ff32ca1080 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--empty={drop,keep,ask}::
+	How to handle commits that become empty (because they contain a
+	subset of already upstream changes) or start empty.  With drop
+	(the default), commits that start or become empty are dropped.
+	With keep (implied by --exec), such commits are kept.  With ask
+	(implied by --interactive), the rebase will halt when an empty
+	commit is applied allowing you to choose whether to drop it or
+	commit it.  Also with ask, if the rebase is interactive then
+	commits which start empty will be commented out in the todo
+	action list (giving you a chance to uncomment).
++
+Note that this has no effect on commits which are already upstream (as
+can be checked via `git log --cherry-mark ...`), which are always
+dropped by rebase.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --keep-empty::
-	Keep the commits that do not change anything from its
-	parents in the result.
+	Deprecated alias for what is now known as --empty=keep.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -569,6 +585,7 @@ are incompatible with the following options:
  * --interactive
  * --exec
  * --keep-empty
+ * --empty=
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --ignore-whitespace
  * --preserve-merges and --committer-date-is-author-date
  * --preserve-merges and --ignore-date
+ * --preserve-merges and --empty=
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES
 
 There are some subtle differences how the backends behave.
 
-Empty commits
-~~~~~~~~~~~~~
-
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
-
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
-
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ddf33bc9d4..96db10eaed 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -50,8 +50,16 @@ enum rebase_type {
 	REBASE_PRESERVE_MERGES
 };
 
+enum empty_type {
+	EMPTY_UNSPECIFIED = -1,
+	EMPTY_DROP,
+	EMPTY_KEEP,
+	EMPTY_ASK
+};
+
 struct rebase_options {
 	enum rebase_type type;
+	enum empty_type empty;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -77,7 +85,6 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
-	int keep_empty;
 	int autosquash;
 	int ignore_whitespace;
 	char *gpg_sign_opt;
@@ -95,6 +102,7 @@ struct rebase_options {
 
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
+		.empty = EMPTY_UNSPECIFIED,	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
 	replay.allow_empty = 1;
 	replay.allow_empty_message = opts->allow_empty_message;
+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
+					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,
 
 	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
-	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_ASK &&
+		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
+			TODO_LIST_ASK_EMPTY : 0;
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,
 	return ret;
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+				int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->empty = EMPTY_KEEP;
+	opts->type = REBASE_INTERACTIVE;
+	return 0;
+}
+
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
-		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
 			 N_("allow commits with empty messages")),
 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		opts->allow_rerere_autoupdate ?
 			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
 			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
+	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
 	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1360,6 +1391,33 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static long parse_empty_value(const char *value)
+{
+	if (!value)
+		return EMPTY_UNSPECIFIED;
+	else if (!strcasecmp(value, "drop"))
+		return EMPTY_DROP;
+	else if (!strcasecmp(value, "keep"))
+		return EMPTY_KEEP;
+	else if (!strcasecmp(value, "ask"))
+		return EMPTY_ASK;
+	return EMPTY_UNSPECIFIED;
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+	long value = parse_empty_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+	if (value < 0)
+		return error(_("option empty accepts \"drop\", "
+			       "\"keep\", and \"ask\""));
+
+	options->empty = value;
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1505,8 +1563,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_BOOL('k', "keep-empty", &options.keep_empty,
-			 N_("preserve empty commits during rebase")),
+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
+			       N_("how to handle empty commits"),
+			       PARSE_OPT_NONEG, parse_opt_empty),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1770,8 +1833,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
-	if (options.keep_empty)
-		imply_interactive(&options, "--keep-empty");
+	if (options.empty != EMPTY_UNSPECIFIED)
+		imply_interactive(&options, "--empty");
 
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
@@ -1856,6 +1919,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
+	if (options.empty == EMPTY_UNSPECIFIED) {
+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+			options.empty = EMPTY_ASK;
+		else if (exec.nr > 0)
+			options.empty = EMPTY_KEEP;
+		else
+			options.empty = EMPTY_DROP;
+	}
 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
diff --git a/rebase-interactive.c b/rebase-interactive.c
index aa18ae82b7..ad82bf77df 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
 	return MISSING_COMMIT_CHECK_IGNORE;
 }
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf)
 {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,
 
 	strbuf_add_commented_lines(buf, msg, strlen(msg));
 
-	if (!keep_empty) {
+	if (!no_ask_empty) {
 		msg = _("Note that empty commits are commented out");
 		strbuf_add_commented_lines(buf, msg, strlen(msg));
 	}
diff --git a/rebase-interactive.h b/rebase-interactive.h
index 44dbb06311..f531e00ba7 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -5,7 +5,7 @@ struct strbuf;
 struct repository;
 struct todo_list;
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf);
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/sequencer.c b/sequencer.c
index 763ccbbc45..d2c11f34b7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
 static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
 
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,
 	empty_commit = is_original_commit_empty(commit);
 	if (empty_commit < 0)
 		return empty_commit;
-	if (!empty_commit)
+	if (!empty_commit || opts->ask_on_initially_empty)
 		return 0;
 	else
 		return 1;
@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,
 	char *author = NULL;
 	struct commit_message msg = { NULL, NULL, NULL, NULL };
 	struct strbuf msgbuf = STRBUF_INIT;
-	int res, unborn = 0, reword = 0, allow;
+	int res, unborn = 0, reword = 0, allow, drop_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,
 		goto leave;
 	}
 
-	allow = allow_empty(r, opts, commit);
-	if (allow < 0) {
-		res = allow;
-		goto leave;
-	} else if (allow)
-		flags |= ALLOW_EMPTY;
-	if (!opts->no_commit) {
+	drop_commit = 0;
+	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
+		drop_commit = 1;
+		fprintf(stderr, _("No changes -- Patch already applied."));
+	} else {
+		allow = allow_empty(r, opts, commit);
+		if (allow < 0) {
+			res = allow;
+			goto leave;
+		} else if (allow) {
+			flags |= ALLOW_EMPTY;
+		}
+	}
+	if (!opts->no_commit && !drop_commit) {
 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 			res = do_commit(r, msg_file, author, opts, flags);
 		else
@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
 	else if (!strcmp(key, "options.allow-empty-message"))
 		opts->allow_empty_message =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.drop-redundant-commits"))
+		opts->drop_redundant_commits =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.keep-redundant-commits"))
 		opts->keep_redundant_commits =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.ask_on_initially_empty"))
+		opts->ask_on_initially_empty =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.signoff"))
 		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.record-origin"))
@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
+		if (file_exists(rebase_path_drop_redundant_commits()))
+			opts->drop_redundant_commits = 1;
+
+		if (file_exists(rebase_path_keep_redundant_commits()))
+			opts->keep_redundant_commits = 1;
+
+		if (file_exists(rebase_path_ask_on_initially_empty()))
+			opts->ask_on_initially_empty = 1;
+
 		read_strategy_opts(opts, &buf);
 		strbuf_release(&buf);
 
@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_cdate_is_adate(), "%s", "");
 	if (opts->ignore_date)
 		write_file(rebase_path_ignore_date(), "%s", "");
+	if (opts->drop_redundant_commits)
+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
+	if (opts->keep_redundant_commits)
+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
+	if (opts->ask_on_initially_empty)
+		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
 	if (opts->allow_empty_message)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.allow-empty-message", "true");
+	if (opts->drop_redundant_commits)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.drop-redundant-commits", "true");
 	if (opts->keep_redundant_commits)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.keep-redundant-commits", "true");
+	if (opts->ask_on_initially_empty)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.ask_on_initially_empty", "true");
 	if (opts->signoff)
 		res |= git_config_set_in_file_gently(opts_file,
 					"options.signoff", "true");
@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 				   struct rev_info *revs, struct strbuf *out,
 				   unsigned flags)
 {
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
 	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		is_empty = is_original_commit_empty(commit);
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
+		if (is_empty && drop_empty)
+			continue;
 
 		strbuf_reset(&oneline);
 		pretty_print_commit(pp, commit, &oneline);
@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		if (!to_merge) {
 			/* non-merge commit: easy case */
 			strbuf_reset(&buf);
-			if (!keep_empty && is_empty)
+			if (is_empty && ask_empty)
 				strbuf_addf(&buf, "%c ", comment_line_char);
 			strbuf_addf(&buf, "%s %s %s", cmd_pick,
 				    oid_to_hex(&commit->object.oid),
@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	struct pretty_print_context pp = {0};
 	struct rev_info revs;
 	struct commit *commit;
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 		return make_script_with_merges(&pp, &revs, out, flags);
 
 	while ((commit = get_revision(&revs))) {
-		int is_empty  = is_original_commit_empty(commit);
+		int is_empty = is_original_commit_empty(commit);
 
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
-		if (!keep_empty && is_empty)
+		if (is_empty && drop_empty)
+			continue;
+		if (is_empty && ask_empty)
 			strbuf_addf(out, "%c ", comment_line_char);
 		strbuf_addf(out, "%s %s ", insn,
 			    oid_to_hex(&commit->object.oid));
@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
 
 	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
 	if (flags & TODO_LIST_APPEND_TODO_HELP)
-		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
+				 count_commands(todo_list),
 				 shortrevisions, shortonto, &buf);
 
 	res = write_message(buf.buf, buf.len, file, 0);
diff --git a/sequencer.h b/sequencer.h
index e9a0e03ea2..1c3abb661c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -39,7 +39,9 @@ struct replay_opts {
 	int allow_rerere_auto;
 	int allow_empty;
 	int allow_empty_message;
+	int drop_redundant_commits;
 	int keep_redundant_commits;
+	int ask_on_initially_empty;
 	int verbose;
 	int quiet;
 	int reschedule_failed_exec;
@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-#define TODO_LIST_KEEP_EMPTY (1U << 0)
+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);
  * `--onto`, we do not want to re-generate the root commits.
  */
 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
+#define TODO_LIST_DROP_EMPTY (1U << 7)
+#define TODO_LIST_ASK_EMPTY (1U << 8)
 
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 325072b0a3..d23e0bf778 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -230,7 +230,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
new file mode 100755
index 0000000000..9d52e1417f
--- /dev/null
+++ b/t/t3424-rebase-empty.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_success 'rebase --merge --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	test_must_fail git rebase --skip &&
+	git commit --allow-empty &&
+	git rebase --continue &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
+
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+
+test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index bec48e6a1f..468ebc1bef 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 02/15] t3406: simplify an already simple test
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

When the merge backend was re-implemented on top of the interactive
backend, the output of rebase --merge changed a little.  This change
allowed this test to be simplified, though it wasn't noticed until now.
Simplify the testcase a little.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3406-rebase-message.sh | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index b393e1e9fe..0c2c569f95 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -18,11 +18,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 21:34   ` Junio C Hamano
  2019-12-20 17:09 ` [PATCH 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
                   ` (13 subsequent siblings)
  16 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The GIT_QUIET environment variable was used to signal the non-am
backends that the rebase should perform quietly.  The preserve-merges
backend does not make use of the quiet flag anywhere (other than to
write out its state whenever it writes state), and this mechanism was
broken in the conversion from shell to C.  Since this environment
variable was specifically designed for scripts and the only backend that
would still use it is no longer a script, just gut this code.

A subsequent commit will fix --quiet for the interactive/merge backend
in a different way.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 6 ++----
 sequencer.c      | 6 ++----
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 96db10eaed..c71d169688 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 	write_file(state_dir_path("orig-head", opts), "%s",
 		   oid_to_hex(&opts->orig_head));
-	write_file(state_dir_path("quiet", opts), "%s",
-		   opts->flags & REBASE_NO_QUIET ? "" : "t");
+	if (!(opts->flags & REBASE_NO_QUIET))
+		write_file(state_dir_path("quiet", opts), "%s", "");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
 	add_var(&script_snippet, "git_am_opt", buf.buf);
 	strbuf_release(&buf);
diff --git a/sequencer.c b/sequencer.c
index d2c11f34b7..71062212a5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const char *orig_head)
 {
-	const char *quiet = getenv("GIT_QUIET");
-
 	if (head_name)
 		write_file(rebase_path_head_name(), "%s\n", head_name);
 	if (onto)
@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 	if (orig_head)
 		write_file(rebase_path_orig_head(), "%s\n", orig_head);
 
-	if (quiet)
-		write_file(rebase_path_quiet(), "%s\n", quiet);
+	if (opts->quiet)
+		write_file(rebase_path_quiet(), "%s", "");
 	if (opts->verbose)
 		write_file(rebase_path_verbose(), "%s", "");
 	if (opts->strategy)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 04/15] rebase: make sure to pass along the quiet flag to the sequencer
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (2 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  | 3 ++-
 t/t3400-rebase.sh | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index c71d169688..cc8f3f008f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
 	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
 					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
+	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -1506,7 +1507,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			 N_("allow pre-rebase hook to run")),
 		OPT_NEGBIT('q', "quiet", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+			   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
 		OPT_BIT('v', "verbose", &options.flags,
 			N_("display a diffstat of what changed upstream"),
 			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 221b35f2df..79762b989a 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
 	git rebase -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	(
 		echo "One" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 05/15] rebase: fix handling of restrict_revision
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (3 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 21:37   ` Junio C Hamano
  2019-12-20 17:09 ` [PATCH 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
                   ` (11 subsequent siblings)
  16 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

restrict_revision in the original shell script was an excluded revision
range.  It is also treated that way by the am-backend.  In the
conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the
interactive-backend accidentally treated it as a positive revision
rather than a negated one.

This was missed as there were no tests in the testsuite that tested an
interactive rebase with fork-point behavior.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  |  4 ++--
 t/t3400-rebase.sh | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index cc8f3f008f..b320bb3a30 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 
 	argv_array_pushl(&make_script_args, "", revisions, NULL);
 	if (opts->restrict_revision)
-		argv_array_push(&make_script_args,
-				oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_pushf(&make_script_args, "^%s",
+				 oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
 				    make_script_args.argc, make_script_args.argv,
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 79762b989a..71fd6396cd 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
 	git rebase master
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
 	git rebase &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 06/15] t3432: make these tests work with either am or merge backends
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (4 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-22  5:11   ` Denton Liu
  2019-12-20 17:09 ` [PATCH 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
                   ` (10 subsequent siblings)
  16 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3432 had several stress tests for can_fast_forward(), whose intent was
to ensure we were using the optimization of just fast forwarding when
possible.  However, these tests verified that fast forwards had happened
based on the output that rebase printed to the terminal.  We can instead
test more directly that we actually fast-forwarded by checking the
reflog, which also has the side effect of making the tests applicable
for the merge/interactive backend.

This change does lose the distinction between "noop" and "noop-force",
but as stated in commit c9efc216830f ("t3432: test for --no-ff's
interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo,
just test for what we're currently doing.".

This change does not actually run these tests with the merge/interactive
backend; instead this is just a preparatory commit.  A subsequent commit
which fixes can_fast_forward() to work with that backend will then also
change t3432 to add tests of that backend as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3432-rebase-fast-forward.sh | 53 ++++++++++++++++------------------
 1 file changed, 25 insertions(+), 28 deletions(-)

diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 92f95b57da..3879a43fa0 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -44,19 +44,16 @@ test_rebase_same_head_ () {
 	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
 		oldhead=\$(git rev-parse HEAD) &&
 		test_when_finished 'git reset --hard \$oldhead' &&
+		cp .git/logs/HEAD expect &&
 		git rebase$flag $* >stdout &&
 		if test $what = work
 		then
-			# Must check this case first, for 'is up to
-			# date, rebase forced[...]rewinding head' cases
-			test_i18ngrep 'rewinding head' stdout
+			wc -l .git/logs/HEAD >old &&
+			wc -l .git/logs/HEAD >new &&
+			test_line_count '-gt' $(($old + 2)) .git/logs/HEAD
 		elif test $what = noop
 		then
-			test_i18ngrep 'is up to date' stdout &&
-			test_i18ngrep ! 'rebase forced' stdout
-		elif test $what = noop-force
-		then
-			test_i18ngrep 'is up to date, rebase forced' stdout
+			test_cmp expect .git/logs/HEAD
 		fi &&
 		newhead=\$(git rev-parse HEAD) &&
 		if test $cmp = same
@@ -71,14 +68,14 @@ test_rebase_same_head_ () {
 
 changes='no changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +88,14 @@ test_expect_success 'add work same to side' '
 
 changes='our changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,13 +109,13 @@ test_expect_success 'add work same to upstream' '
 '
 
 changes='our and their changes'
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
 test_rebase_same_head success noop same success work diff --onto master... master
 test_rebase_same_head success noop same success work diff --keep-base master
 test_rebase_same_head success noop same success work diff --keep-base
-test_rebase_same_head failure work same success work diff --fork-point --onto B B
-test_rebase_same_head failure work same success work diff --fork-point --onto B... B
+test_rebase_same_head success work same success work diff --fork-point --onto B B
+test_rebase_same_head success work same success work diff --fork-point --onto B... B
 test_rebase_same_head success noop same success work diff --fork-point --onto master... master
 test_rebase_same_head success noop same success work diff --fork-point --keep-base master
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 07/15] rebase: allow more types of rebases to fast-forward
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (5 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we dis-allowed rebases using the interactive backend from
performing a fast-forward to short-circuit the rebase operation.  This
made sense for explicitly interactive rebases and some implicitly
interactive rebases, but certainly became overly stringent when the
merge backend was re-implemented via the interactive backend.

Just as the am-based rebase has always had to disable the fast-forward
based on a variety of conditions or flags (e.g. --signoff, --whitespace,
etc.), we need to do the same but now with a few more options.  However,
continuing to use REBASE_FORCE for tracking this is problematic because
the interactive backend used it for a different purpose.  (When
REBASE_FORCE wasn't set, the interactive backend would not fast-forward
the whole series but would fast-forward individual "pick" commits at the
beginning of the todo list, and then a squash or something would cause
it to start generating new commits.)  So, introduce a new
allow_preemptive_ff flag contained within cmd_rebase() and use it to
track whether we are going to allow a pre-emptive fast-forward that
short-circuits the whole rebase.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c               | 18 ++++++++++++++----
 t/t3432-rebase-fast-forward.sh |  2 ++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index b320bb3a30..67bccd876f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1497,6 +1497,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
+	int allow_preemptive_ff = 1;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1808,11 +1809,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	    options.ignore_date)
 		options.flags |= REBASE_FORCE;
 
+	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+	    (action != ACTION_NONE) ||
+	    (exec.nr > 0) ||
+	    options.autosquash) {
+		allow_preemptive_ff = 0;
+	}
+
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
 		if (!strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
-			options.flags |= REBASE_FORCE;
+			allow_preemptive_ff = 0;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -2148,12 +2156,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	/*
 	 * Check if we are already based on onto with linear history,
 	 * in which case we could fast-forward without replacing the commits
-	 * with new commits recreated by replaying their changes. This
-	 * optimization must not be done if this is an interactive rebase.
+	 * with new commits recreated by replaying their changes.
+	 *
+	 * Note that can_fast_forward() initializes merge_base, so we have to
+	 * call it before checking allow_preemptive_ff.
 	 */
 	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
 		    &options.orig_head, &merge_base) &&
-	    !is_interactive(&options)) {
+	    allow_preemptive_ff) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 3879a43fa0..58c91c6899 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -30,6 +30,8 @@ test_rebase_same_head () {
 	shift &&
 	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 08/15] git-rebase.txt: add more details about behavioral differences of backends
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (6 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---
 t/t3433-rebase-options-compatibility.sh |   5 +-
 2 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index ff32ca1080..f1ace07c38 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used
 with `--keep-base` in order to drop those commits from your branch.
 
 --ignore-whitespace::
-	Behaves differently depending on which backend is selected.
-+
-'am' backend: When applying a patch, ignore changes in whitespace in
-context lines if necessary.
-+
-'interactive' backend: Treat lines with only whitespace changes as
-unchanged for the sake of a three-way merge.
+	Ignore whitespace-only changes in the commits being rebased,
+	which may avoid "unnecessary" conflicts.  (Both backends
+	currently have differing edgecase bugs with this option; see
+	BEHAVIORAL DIFFERENCES.)
 
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Directory rename heuristics are enabled in the merge and interactive
-backends.  Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the am backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge and interactive
+backends to provide you warnings in such cases.
+
+Context
+~~~~~~~
+
+The am backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally).  Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes.  The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file.  The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines.  However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked.  There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The interactive backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from.  Since the am backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary.  Also, when merge.conflictStyle is
+set to diff3, the am backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The interactive backend works with the full commits on both sides of
+history and thus has no such limitations.
+
+--ignore-whitespace
+~~~~~~~~~~~~~~~~~~~
+
+The --ignore-whitespace option is supposed to ignore whitespace-only
+changes if it allows the code to merge cleanly.  Unfortunately, the
+different backends implement this differently, and both have different
+edge case bugs.
++
+'am' backend: When applying a patch, ignore changes in whitespace in
+context lines if necessary.  (Which implies that if the whitespace
+change was not in the context lines but on a line with a real change,
+then the rebase will still fail with "unnecessary" content conflicts.)
++
+'interactive' backend: Treat lines with only whitespace changes as
+unchanged for the sake of a three-way merge.  This means that if one
+side made no changes and the commits being rebased had whitespace-only
+changes, those whitespaces fixups will be discarded despite the fact
+that they present no content conflict.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+  the changes made in the reflog, though both will make use of the
+  word "rebase".
+
+* Progress, informational, and error messages: The two backends
+  provide slightly different progress and informational messages.
+  Also, the am backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the interactive backend
+  writes them to stderr.
+
+* State directories: The two backends keep their state in different
+  directories under .git/
 
 include::merge-strategies.txt[]
 
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index 5166f158dd..bd4d2d2f63 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe
 GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
 export GIT_AUTHOR_DATE
 
-# This is a special case in which both am and interactive backends
-# provide the same output. It was done intentionally because
-# both the backends fall short of optimal behaviour.
+# This is a common case in which both am and interactive backends
+# provide the same output with --ignore-whitespace.
 test_expect_success 'setup' '
 	git checkout -b topic &&
 	q_to_tab >file <<-\EOF &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 09/15] rebase: move incompatibility checks between backend options a bit earlier
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (7 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 67bccd876f..9e7e88b147 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1910,6 +1910,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
+	if (options.git_am_opts.argc) {
+		/* all am options except -q are compatible only with --am */
+		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+			if (strcmp(options.git_am_opts.argv[i], "-q"))
+				break;
+
+		if (is_interactive(&options) && i >= 0)
+			die(_("cannot combine am options with either "
+			      "interactive or merge options"));
+	}
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
@@ -1940,17 +1951,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (reschedule_failed_exec >= 0)
 		options.reschedule_failed_exec = reschedule_failed_exec;
 
-	if (options.git_am_opts.argc) {
-		/* all am options except -q are compatible only with --am */
-		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
-			if (strcmp(options.git_am_opts.argv[i], "-q"))
-				break;
-
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
-	}
-
 	if (options.signoff) {
 		if (options.type == REBASE_PRESERVE_MERGES)
 			die("cannot combine '--signoff' with "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 10/15] rebase: add an --am option
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (8 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 11/15] contrib: change the prompt for am-based rebases Elijah Newren via GitGitGadget
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Currently, this option doesn't do anything except error out if any
options requiring the interactive-backend are also passed.  However,
when we make the default backend configurable later in this series, this
flag will provide a way to override the config setting.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 11 ++++++++++-
 builtin/rebase.c             | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index f1ace07c38..cf1ac2e359 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--am:
+	Use git-am internally to rebase.  This option may become a
+	no-op in the future once the interactive backend handles
+	everything the am one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --empty={drop,keep,ask}::
 	How to handle commits that become empty (because they contain a
 	subset of already upstream changes) or start empty.  With drop
@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.
+	ever ignored.  Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
+	Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
+ * --am
  * --whitespace
  * -C
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 9e7e88b147..ab9e16b206 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	return res && is_linear_history(onto, head);
 }
 
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_AM;
+
+	return 0;
+}
+
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1550,6 +1562,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
+		{ OPTION_CALLBACK, 0, "am", &options, NULL,
+			N_("use apply-mail strategies to rebase"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
 			N_("use merging strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1910,7 +1926,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc) {
+	if (options.git_am_opts.argc || options.type == REBASE_AM) {
 		/* all am options except -q are compatible only with --am */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 11/15] contrib: change the prompt for am-based rebases
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (9 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 23:07   ` SZEDER Gábor
  2019-12-20 17:09 ` [PATCH 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
                   ` (5 subsequent siblings)
  16 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The prompt for am-based rebases was REBASE, while for interactive-based
rebases was REBASE-i.  A while ago, we switched merge-based rebases from
using REBASE-m to REBASE-i via re-implementing the merge backend based
on the interactive backend.  We will soon be changing the default rebase
backend to the interactive one, meaning the default prompt will be
REBASE-i rather than REBASE.  We have also noted in the documentation
that currently am-specific options will be implemented in the
interactive backend, and even the --am flag may eventually imply an
interactive-based rebase.  As such, change the prompt for an am-based
rebase from REBASE to REBASE-a.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 contrib/completion/git-prompt.sh | 2 +-
 t/t9903-bash-prompt.sh           | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd47b..3c81099d60 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -440,7 +440,7 @@ __git_ps1 ()
 			__git_eread "$g/rebase-apply/last" total
 			if [ -f "$g/rebase-apply/rebasing" ]; then
 				__git_eread "$g/rebase-apply/head-name" b
-				r="|REBASE"
+				r="|REBASE-a"
 			elif [ -f "$g/rebase-apply/applying" ]; then
 				r="|AM"
 			else
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 88bc733ad6..8da5b1aee2 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
-	printf " (b2|REBASE 1/3)" >expected &&
+test_expect_success 'prompt - rebase am' '
+	printf " (b2|REBASE-a 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --am b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 12/15] rebase tests: mark tests specific to the am-backend with --am
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (10 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 11/15] contrib: change the prompt for am-based rebases Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
                   ` (4 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

We have many rebase tests in the testsuite, and often the same test is
repeated multiple times just testing different backends.  For those
tests that were specifically trying to test the am backend, add the --am
flag.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3400-rebase.sh                       | 10 +++++-----
 t/t3401-rebase-and-am-rename.sh         |  4 ++--
 t/t3404-rebase-interactive.sh           |  2 +-
 t/t3406-rebase-message.sh               | 12 ++++++------
 t/t3407-rebase-abort.sh                 |  6 +++---
 t/t3420-rebase-autostash.sh             |  2 +-
 t/t3425-rebase-topology-merges.sh       |  8 ++++----
 t/t3432-rebase-fast-forward.sh          |  4 ++--
 t/t3433-rebase-options-compatibility.sh |  8 ++++----
 t/t5407-post-rewrite-hook.sh            | 12 ++++++------
 t/t7512-status-help.sh                  | 12 ++++++------
 11 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 71fd6396cd..0a491f2363 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 
 test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --am -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase -f --onto init HEAD^ &&
+		test_must_fail git rebase --am -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index a0b9438b22..50803958fd 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --am: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --am A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ae6e55ce79..743b7e511a 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 0c2c569f95..7ce617fc1f 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --am master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --am topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase reflog-onto &&
+	git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 910f218284..3e31826170 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --am --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --am master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 5f7e73cf83..3816159e20 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index fd8efe84fe..19700b025b 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --am
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 58c91c6899..b22aa8c7ee 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index bd4d2d2f63..a07e1f276b 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '
 	new line 2
 	line 3
 	EOF
-	test_must_fail git rebase main side &&
+	test_must_fail git rebase --am main side &&
 	git rebase --abort &&
-	git rebase --ignore-whitespace main side &&
+	git rebase --am --ignore-whitespace main side &&
 	test_cmp expect file
 '
 
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '
 
 test_expect_success '--committer-date-is-author-date works with am backend' '
 	git commit --amend &&
-	git rebase --committer-date-is-author-date HEAD^ &&
+	git rebase --am --committer-date-is-author-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	git show HEAD --pretty="format:%ci" >committertime &&
 	test_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '
 # sets to +0530.
 test_expect_success '--ignore-date works with am backend' '
 	git commit --amend --date="$GIT_AUTHOR_DATE" &&
-	git rebase --ignore-date HEAD^ &&
+	git rebase --am --ignore-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	grep "+0000" authortime
 '
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 7344253bfb..a8a73616e4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --am' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --am --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --am --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --am --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 66d7a62797..d22b0acf2a 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --am in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --am in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --am conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 13/15] rebase tests: repeat some tests using the merge backend instead of am
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (11 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In order to ensure the merge/interactive backend gets similar coverage
to the am one, add some tests for cases where previously only the am
backend was tested.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t5520-pull.sh                   | 17 +++++++++++++++--
 t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 602d996a33..3fff6a06fa 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -277,14 +277,27 @@ test_expect_success '--rebase' '
 	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=am pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index 860542aad0..d383ce8130 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '
 	)
 '
 
-test_expect_success 'rebase describes fake ancestor base' '
+test_expect_success 'rebase --merge describes parent of commit being picked' '
 	test_create_repo rebase &&
 	(
 		cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '
 		test_commit master file &&
 		git checkout -b side HEAD^ &&
 		test_commit side file &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --am describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
 		grep "||||||| constructed merge base" file
 	)
 '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 14/15] rebase: make the backend configurable via config setting
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (12 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 17:09 ` [PATCH 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt |  8 ++++++++
 builtin/rebase.c                | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d812..e6ae30c999 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,14 @@ rebase.useBuiltin::
 	is always used. Setting this will emit a warning, to alert any
 	remaining users that setting this now does nothing.
 
+rebase.backend::
+	Default backend to use for rebasing.  Possible choices are
+	'am' or 'merge' (note that the merge backend is sometimes also
+	refered to as the interactive backend or the interactive
+	machinery elsewhere in the docs).  Also, in the future, if the
+	merge backend gains all remaining capabilities of the am
+	backend, this setting may become unused.
+
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
 	rebase. False by default.
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ab9e16b206..eaa5eac59e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -60,6 +60,7 @@ enum empty_type {
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
+	const char *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -103,6 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
+		.default_backend = "am",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.backend")) {
+		return git_config_string(&opts->default_backend, var, value);
+	}
+
 	return git_default_config(var, value, data);
 }
 
@@ -1932,9 +1938,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
+		if (i >= 0) {
+			if (is_interactive(&options))
+				die(_("cannot combine am options with either "
+				      "interactive or merge options"));
+			else
+				options.type = REBASE_AM;
+		}
+	}
+
+	if (options.type == REBASE_UNSPECIFIED) {
+		if (!strcmp(options.default_backend, "merge"))
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "am"))
+			options.type = REBASE_AM;
+		else
+			die(_("Unknown rebase backend: %s"),
+			    options.default_backend);
 	}
 
 	switch (options.type) {
@@ -1947,10 +1967,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		options.state_dir = apply_dir();
 		break;
 	default:
-		/* the default rebase backend is `--am` */
-		options.type = REBASE_AM;
-		options.state_dir = apply_dir();
-		break;
+		BUG("options.type was just set above; should be unreachable.");
 	}
 
 	if (options.empty == EMPTY_UNSPECIFIED) {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH 15/15] rebase: change the default backend from "am" to "merge"
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (13 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
@ 2019-12-20 17:09 ` Elijah Newren via GitGitGadget
  2019-12-20 18:51 ` [PATCH 00/15] rebase: make the default backend configurable Alban Gruin
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
  16 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-20 17:09 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The am-backend drops information and thus limits what we can do:

  * lack of full tree information from the original commits means we
    cannot do directory rename detection and warn users that they might
    want to move some of their new files that they placed in old
    directories to prevent their becoming orphaned.[1]
  * reduction in context from only having a few lines beyond those
    changed means that when context lines are non-unique we can apply
    patches incorrectly.[2]
  * lack of access to original commits means that conflict marker
    annotation has less information available.

Also, the merge/interactive backend have far more abilities, appear to
currently have a slight performance advantage[3] and have room for more
optimizations than the am backend[4] (and work is underway to take
advantage of some of those possibilities).

[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt           |  2 +-
 builtin/rebase.c                       |  4 ++--
 t/t5520-pull.sh                        | 10 ++++++----
 t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index cf1ac2e359..e819889a31 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.
 --merge::
 	Use merging strategies to rebase.  When the recursive (default) merge
 	strategy is used, this allows rebase to be aware of renames on the
-	upstream side.
+	upstream side.  This is the default.
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
diff --git a/builtin/rebase.c b/builtin/rebase.c
index eaa5eac59e..1c3d0b97d5 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -104,7 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "am",	  	\
+		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1949,7 +1949,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			options.type = REBASE_MERGE;
+			imply_interactive(&options, "--merge");
 		else if (!strcmp(options.default_backend, "am"))
 			options.type = REBASE_AM;
 		else
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3fff6a06fa..4f9e7f7ff6 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
 	(
 		cd dst &&
 		test_must_fail git pull --rebase &&
-		find .git/rebase-apply -name "000*" >patches &&
-		test_line_count = 1 patches
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d..aec45bca3b 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH 00/15] rebase: make the default backend configurable
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (14 preceding siblings ...)
  2019-12-20 17:09 ` [PATCH 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2019-12-20 18:51 ` Alban Gruin
  2019-12-20 18:55   ` Elijah Newren
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
  16 siblings, 1 reply; 161+ messages in thread
From: Alban Gruin @ 2019-12-20 18:51 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin

Hi Elijah,

Le 20/12/2019 à 18:09, Elijah Newren via GitGitGadget a écrit :
> This series does a lot of work around making the default rebase backend
> configurable:
> 
>  * provide configurability of handling empty commits and iron out
>    differences between backends
>  * increase/fix capabilities of the merge/interactive backend to make it
>    workable as the default
>  * document the remaining differences in backends more thoroughly
>  * add an --am option for explicitly requesting the am-backend
>  * extend merge/interactive backend testing to put it on par with the am
>    backend
>  * add a 'rebase.backend' config option and:
>  * switch the rebase.backend default value from 'am' to 'merge'.
> 
> Areas I'd like reviewers to focus, in priority order:
> 
>  * Patch 15 (Are we ready to switch the default backend? Or should we leave
>    this patch out for now? Junio suggested we may be ready or at least are
>    close[1])
>  * Patch 1 (Does my empty handling make sense? Do others agree I fixed 
>    --keep-empty, or do they view it as breaking it?)
>  * Patch 8 (Do the updates to the documentation of behavioral differences
>    make sense? Is it too long?) * Patch 11 (okay to change the
>       git-completion shell prompt slightly, especially in light of patches
>       15 & 16? We did a prompt change previously when we merged the merge
>       backend with the interactive one, so I assume so, but just want to
>       make sure people have a chance to chime in.)
>    
>    
> 
> If it's too soon to switch from the 'am' to 'merge' backend, we can just
> drop the last patch and I'll resubmit it later. I generated this series
> mostly through switching the default first and then watching what broke, but
> moved the patch to the end to make it easy to drop.
> 
> Briefly, reasons for switching the default backend boil down to the fact
> that the am-backend drops information and thus limits what it can do. This
> manifests in different ways:
> 
>  * lack of tree information that would allow us to warn users that new files
>    in old directories might want to move along with the other files that
>    were renamed with those directories[1]
>  * incorrect application of patches in the presence of non-unique context
>    lines[2], which could be avoided with access to the original files
>    involved.
>  * less information available to annotate conflict markers (since am creates
>    fake ancestors and commits on top of them, and doesn't have access to the
>    original commits)
> 
> [1] https://lore.kernel.org/git/xmqqa78d2qmk.fsf@gitster-ct.c.googlers.com/
> [2] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
> [3] 
> https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
> 
> Elijah Newren (15):
>   rebase: extend the options for handling of empty commits
>   t3406: simplify an already simple test
>   rebase, sequencer: remove the broken GIT_QUIET handling
>   rebase: make sure to pass along the quiet flag to the sequencer
>   rebase: fix handling of restrict_revision
>   t3432: make these tests work with either am or merge backends
>   rebase: allow more types of rebases to fast-forward
>   git-rebase.txt: add more details about behavioral differences of
>     backends
>   rebase: move incompatibility checks between backend options a bit
>     earlier
>   rebase: add an --am option
>   contrib: change the prompt for am-based rebases
>   rebase tests: mark tests specific to the am-backend with --am
>   rebase tests: repeat some tests using the merge backend instead of am
>   rebase: make the backend configurable via config setting
>   rebase: change the default backend from "am" to "merge"
> 
>  Documentation/config/rebase.txt         |   8 ++
>  Documentation/git-rebase.txt            | 150 ++++++++++++++++----
>  builtin/rebase.c                        | 181 +++++++++++++++++++-----
>  contrib/completion/git-prompt.sh        |   2 +-
>  rebase-interactive.c                    |   4 +-
>  rebase-interactive.h                    |   2 +-
>  sequencer.c                             |  80 ++++++++---
>  sequencer.h                             |   6 +-
>  t/t3400-rebase.sh                       |  36 ++++-
>  t/t3401-rebase-and-am-rename.sh         |   4 +-
>  t/t3404-rebase-interactive.sh           |   2 +-
>  t/t3406-rebase-message.sh               |  19 ++-
>  t/t3407-rebase-abort.sh                 |   6 +-
>  t/t3420-rebase-autostash.sh             |   2 +-
>  t/t3421-rebase-topology-linear.sh       |   4 +-
>  t/t3424-rebase-empty.sh                 |  89 ++++++++++++
>  t/t3425-rebase-topology-merges.sh       |   8 +-
>  t/t3427-rebase-subtree.sh               |  16 ++-
>  t/t3432-rebase-fast-forward.sh          |  59 ++++----
>  t/t3433-rebase-options-compatibility.sh |  13 +-
>  t/t5407-post-rewrite-hook.sh            |  12 +-
>  t/t5520-pull.sh                         |  27 +++-
>  t/t6047-diff3-conflict-markers.sh       |  13 +-
>  t/t7512-status-help.sh                  |  12 +-
>  t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-
>  t/t9903-bash-prompt.sh                  |   6 +-
>  26 files changed, 582 insertions(+), 182 deletions(-)
>  create mode 100755 t/t3424-rebase-empty.sh
> 
> 
> base-commit: 12029dc57db23baef008e77db1909367599210ee
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1
> Pull-Request: https://github.com/git/git/pull/679
> 

BTW, I have investigated on the performance regression when split-index
is enabled, I’ll give my conclusions next week.

Cheers,
Alban


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 00/15] rebase: make the default backend configurable
  2019-12-20 18:51 ` [PATCH 00/15] rebase: make the default backend configurable Alban Gruin
@ 2019-12-20 18:55   ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2019-12-20 18:55 UTC (permalink / raw)
  To: Alban Gruin
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin

Hi Alban,

On Fri, Dec 20, 2019 at 10:53 AM Alban Gruin <alban.gruin@gmail.com> wrote:
>
> Hi Elijah,
>
> Le 20/12/2019 à 18:09, Elijah Newren via GitGitGadget a écrit :
> > This series does a lot of work around making the default rebase backend
> > configurable:
> >
>
> BTW, I have investigated on the performance regression when split-index
> is enabled, I’ll give my conclusions next week.
>
> Cheers,
> Alban

Nice!

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
  2019-12-20 17:09 ` [PATCH 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
@ 2019-12-20 21:29   ` Junio C Hamano
  2019-12-21  0:32     ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2019-12-20 21:29 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, plroskin,
	Elijah Newren

"Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Elijah Newren <newren@gmail.com>
>
> Extend the interactive machinery with the ability to handle the full
> spread of options for how to handle commits that either start or become
> empty (by "become empty" I mean the changes in a commit are a subset of
> changes that exist upstream, so the net effect of applying the commit is
> no changes).  Introduce a new command line flag for selecting the
> desired behavior:
>     --empty={drop,keep,ask}
> with the definitions:
>     drop: drop empty commits
>     keep: keep empty commits
>     ask:  provide the user a chance to interact and pick what to do with
>           empty commits on a case-by-case basis

This looks like a logical and natural extension of the --keep-empty
option.

After seeing the stress on "empty from the beginning and ending up
to be empty" in the description, I somehow expected that we may be
able to specify what happens to the empty commit separately, but
that does not seem to be what the patch is about, which was somewhat
disappointing.

> +static long parse_empty_value(const char *value)
> +{
> +	if (!value)
> +		return EMPTY_UNSPECIFIED;
> +	else if (!strcasecmp(value, "drop"))
> +		return EMPTY_DROP;
> +	else if (!strcasecmp(value, "keep"))
> +		return EMPTY_KEEP;
> +	else if (!strcasecmp(value, "ask"))
> +		return EMPTY_ASK;

Not an error but just silently ignored?

> +	return EMPTY_UNSPECIFIED;
> +}
> +

> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +	long value = parse_empty_value(arg);

Ahh, OK.

Wouldn't it be better to make the variable and the parsing helper
function of type "enum empty_type", not "long", just like the field
in the rebase_options struct?

> +	BUG_ON_OPT_NEG(unset);
> +	if (value < 0)
> +		return error(_("option empty accepts \"drop\", "
> +			       "\"keep\", and \"ask\""));
> +
> +	options->empty = value;
> +	return 0;
> +}

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling
  2019-12-20 17:09 ` [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2019-12-20 21:34   ` Junio C Hamano
  0 siblings, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2019-12-20 21:34 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, plroskin,
	Elijah Newren

"Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Elijah Newren <newren@gmail.com>
>
> The GIT_QUIET environment variable was used to signal the non-am
> backends that the rebase should perform quietly.  The preserve-merges
> backend does not make use of the quiet flag anywhere (other than to
> write out its state whenever it writes state), and this mechanism was
> broken in the conversion from shell to C.  Since this environment
> variable was specifically designed for scripts and the only backend that
> would still use it is no longer a script, just gut this code.

Nice to see a useless code go ;-)

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 05/15] rebase: fix handling of restrict_revision
  2019-12-20 17:09 ` [PATCH 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2019-12-20 21:37   ` Junio C Hamano
  0 siblings, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2019-12-20 21:37 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, plroskin,
	Elijah Newren

"Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Elijah Newren <newren@gmail.com>
>
> restrict_revision in the original shell script was an excluded revision
> range.  It is also treated that way by the am-backend.  In the
> conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
> implement the logic to initialize $revisions in C", 2018-08-28)), the
> interactive-backend accidentally treated it as a positive revision
> rather than a negated one.
>
> This was missed as there were no tests in the testsuite that tested an
> interactive rebase with fork-point behavior.

Thanks.

> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>  builtin/rebase.c  |  4 ++--
>  t/t3400-rebase.sh | 20 +++++++++++++++++++-
>  2 files changed, 21 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index cc8f3f008f..b320bb3a30 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
>  
>  	argv_array_pushl(&make_script_args, "", revisions, NULL);
>  	if (opts->restrict_revision)
> -		argv_array_push(&make_script_args,
> -				oid_to_hex(&opts->restrict_revision->object.oid));
> +		argv_array_pushf(&make_script_args, "^%s",
> +				 oid_to_hex(&opts->restrict_revision->object.oid));
>  
>  	ret = sequencer_make_script(the_repository, &todo_list.buf,
>  				    make_script_args.argc, make_script_args.argv,
> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
> index 79762b989a..71fd6396cd 100755
> --- a/t/t3400-rebase.sh
> +++ b/t/t3400-rebase.sh
> @@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
>  	git rebase master
>  '
>  
> -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
> +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
>  	git checkout -b default-base master &&
>  	git checkout -b default topic &&
>  	git config branch.default.remote . &&
>  	git config branch.default.merge refs/heads/default-base &&
> +	git rebase --merge &&
> +	git rev-parse --verify default-base >expect &&
> +	git rev-parse default~1 >actual &&
> +	test_cmp expect actual &&
> +	git checkout default-base &&
> +	git reset --hard HEAD^ &&
> +	git checkout default &&
> +	git rebase --merge &&
> +	git rev-parse --verify default-base >expect &&
> +	git rev-parse default~1 >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
> +	git checkout -B default-base master &&
> +	git checkout -B default topic &&
> +	git config branch.default.remote . &&
> +	git config branch.default.merge refs/heads/default-base &&
>  	git rebase &&
>  	git rev-parse --verify default-base >expect &&
>  	git rev-parse default~1 >actual &&

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 11/15] contrib: change the prompt for am-based rebases
  2019-12-20 17:09 ` [PATCH 11/15] contrib: change the prompt for am-based rebases Elijah Newren via GitGitGadget
@ 2019-12-20 23:07   ` SZEDER Gábor
  2019-12-21  0:17     ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: SZEDER Gábor @ 2019-12-20 23:07 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, gitster,
	plroskin, Elijah Newren

The subject prefix for this file is most of the time 'bash prompt' or
'git-prompt'.

On Fri, Dec 20, 2019 at 05:09:44PM +0000, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> The prompt for am-based rebases was REBASE, while for interactive-based
> rebases was REBASE-i.  A while ago, we switched merge-based rebases from
> using REBASE-m to REBASE-i via re-implementing the merge backend based
> on the interactive backend.  We will soon be changing the default rebase
> backend to the interactive one, meaning the default prompt will be
> REBASE-i rather than REBASE.  We have also noted in the documentation
> that currently am-specific options will be implemented in the
> interactive backend, and even the --am flag may eventually imply an
> interactive-based rebase.  As such, change the prompt for an am-based
> rebase from REBASE to REBASE-a.

I had a bit of a hard time following which rebase variant is
implemented with which backend and when it was changed, and... etc.

Makes me wonder: do we really need those "-i", "-m" or "-a" suffixes?
What benefit do they bring?  Why don't we just say "REBASE" in the
prompt?

> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>  contrib/completion/git-prompt.sh | 2 +-
>  t/t9903-bash-prompt.sh           | 6 +++---
>  2 files changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
> index 1d510cd47b..3c81099d60 100644
> --- a/contrib/completion/git-prompt.sh
> +++ b/contrib/completion/git-prompt.sh
> @@ -440,7 +440,7 @@ __git_ps1 ()
>  			__git_eread "$g/rebase-apply/last" total
>  			if [ -f "$g/rebase-apply/rebasing" ]; then
>  				__git_eread "$g/rebase-apply/head-name" b
> -				r="|REBASE"
> +				r="|REBASE-a"
>  			elif [ -f "$g/rebase-apply/applying" ]; then
>  				r="|AM"
>  			else
> diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
> index 88bc733ad6..8da5b1aee2 100755
> --- a/t/t9903-bash-prompt.sh
> +++ b/t/t9903-bash-prompt.sh
> @@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
>  	test_cmp expected "$actual"
>  '
>  
> -test_expect_success 'prompt - rebase' '
> -	printf " (b2|REBASE 1/3)" >expected &&
> +test_expect_success 'prompt - rebase am' '
> +	printf " (b2|REBASE-a 1/3)" >expected &&
>  	git checkout b2 &&
>  	test_when_finished "git checkout master" &&
> -	test_must_fail git rebase b1 b2 &&
> +	test_must_fail git rebase --am b1 b2 &&
>  	test_when_finished "git rebase --abort" &&
>  	__git_ps1 >"$actual" &&
>  	test_cmp expected "$actual"
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 11/15] contrib: change the prompt for am-based rebases
  2019-12-20 23:07   ` SZEDER Gábor
@ 2019-12-21  0:17     ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2019-12-21  0:17 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin

On Fri, Dec 20, 2019 at 3:07 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
>
> The subject prefix for this file is most of the time 'bash prompt' or
> 'git-prompt'.
>
> On Fri, Dec 20, 2019 at 05:09:44PM +0000, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > The prompt for am-based rebases was REBASE, while for interactive-based
> > rebases was REBASE-i.  A while ago, we switched merge-based rebases from
> > using REBASE-m to REBASE-i via re-implementing the merge backend based
> > on the interactive backend.  We will soon be changing the default rebase
> > backend to the interactive one, meaning the default prompt will be
> > REBASE-i rather than REBASE.  We have also noted in the documentation
> > that currently am-specific options will be implemented in the
> > interactive backend, and even the --am flag may eventually imply an
> > interactive-based rebase.  As such, change the prompt for an am-based
> > rebase from REBASE to REBASE-a.
>
> I had a bit of a hard time following which rebase variant is
> implemented with which backend and when it was changed, and... etc.

Sorry about that.  I could fix it up, but...

> Makes me wonder: do we really need those "-i", "-m" or "-a" suffixes?
> What benefit do they bring?  Why don't we just say "REBASE" in the
> prompt?

Ooh, I like this idea.  A lot easier to explain in the commit message
and probably less confusing for users as well.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
  2019-12-20 21:29   ` Junio C Hamano
@ 2019-12-21  0:32     ` Elijah Newren
  2019-12-21 18:52       ` Elijah Newren
  2019-12-21 23:49       ` Junio C Hamano
  0 siblings, 2 replies; 161+ messages in thread
From: Elijah Newren @ 2019-12-21  0:32 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin

On Fri, Dec 20, 2019 at 1:29 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: Elijah Newren <newren@gmail.com>
> >
> > Extend the interactive machinery with the ability to handle the full
> > spread of options for how to handle commits that either start or become
> > empty (by "become empty" I mean the changes in a commit are a subset of
> > changes that exist upstream, so the net effect of applying the commit is
> > no changes).  Introduce a new command line flag for selecting the
> > desired behavior:
> >     --empty={drop,keep,ask}
> > with the definitions:
> >     drop: drop empty commits
> >     keep: keep empty commits
> >     ask:  provide the user a chance to interact and pick what to do with
> >           empty commits on a case-by-case basis
>
> This looks like a logical and natural extension of the --keep-empty
> option.
>
> After seeing the stress on "empty from the beginning and ending up
> to be empty" in the description, I somehow expected that we may be
> able to specify what happens to the empty commit separately, but
> that does not seem to be what the patch is about, which was somewhat
> disappointing.

My main reason for stressing both was that I've commits and code that
talked about "empty" commits, but which often only dealt with one of
the two types (sometimes when I would expect both to have been
addressed), so I was just trying to be careful in my explanation of
what it was about.

If wanted, we could implement finer control, but I don't know what the
UI would look like or what the range of options we might want.  Do we
want to mix all possible permutations and have two flags, e.g.
--become-empty=drop --start-empty=keep or --start-empty=ask
--become-empty=keep?  Would --empty=<whatever> imply both
--become-empty=<whatever> and --start-empty=<whatever> or would users
need to specify both?  Do we think all the permutations make sense, or
would we want to limit users to some subset?

The current choice of three covered the things that I had wanted in
the past, and which I think made for good defaults for various
different flags, so I just implemented it.  We could always extend it
later.

>
> > +static long parse_empty_value(const char *value)
> > +{
> > +     if (!value)
> > +             return EMPTY_UNSPECIFIED;
> > +     else if (!strcasecmp(value, "drop"))
> > +             return EMPTY_DROP;
> > +     else if (!strcasecmp(value, "keep"))
> > +             return EMPTY_KEEP;
> > +     else if (!strcasecmp(value, "ask"))
> > +             return EMPTY_ASK;
>
> Not an error but just silently ignored?

Oops, I'll switch it to an error.

> > +     return EMPTY_UNSPECIFIED;
> > +}
> > +
>
> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
> > +{
> > +     struct rebase_options *options = opt->value;
> > +     long value = parse_empty_value(arg);
>
> Ahh, OK.
>
> Wouldn't it be better to make the variable and the parsing helper
> function of type "enum empty_type", not "long", just like the field
> in the rebase_options struct?

Indeed, I'll fix this up.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
  2019-12-21  0:32     ` Elijah Newren
@ 2019-12-21 18:52       ` Elijah Newren
  2019-12-21 23:49       ` Junio C Hamano
  1 sibling, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2019-12-21 18:52 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin

On Fri, Dec 20, 2019 at 4:32 PM Elijah Newren <newren@gmail.com> wrote:
>
> On Fri, Dec 20, 2019 at 1:29 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > "Elijah Newren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> >

> > > +static long parse_empty_value(const char *value)
> > > +{
> > > +     if (!value)
> > > +             return EMPTY_UNSPECIFIED;
> > > +     else if (!strcasecmp(value, "drop"))
> > > +             return EMPTY_DROP;
> > > +     else if (!strcasecmp(value, "keep"))
> > > +             return EMPTY_KEEP;
> > > +     else if (!strcasecmp(value, "ask"))
> > > +             return EMPTY_ASK;
> >
> > Not an error but just silently ignored?
>
> Oops, I'll switch it to an error.

By the way, diff.c's parse_algorithm_value() and
diff_opt_diff_algorithm() behave the same way, where
parse_algorithm_value() will return a negative value if it's not one
of the valid known cases, and diff_opt_diff_algorithm() checks for
negative and throws an error.  Should I also modify those to put the
error in parse_algorithm_value()?  (Not part of this series, but as a
consistency cleanup since it clearly surprised you and even I forgot
that I had the calling function returning the error when I copied that
style?)

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
  2019-12-21  0:32     ` Elijah Newren
  2019-12-21 18:52       ` Elijah Newren
@ 2019-12-21 23:49       ` Junio C Hamano
  1 sibling, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2019-12-21 23:49 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin

Elijah Newren <newren@gmail.com> writes:

>> > +static long parse_empty_value(const char *value)
>> > +{
>> > +     if (!value)
>> > +             return EMPTY_UNSPECIFIED;
>> > +     else if (!strcasecmp(value, "drop"))
>> > +             return EMPTY_DROP;
>> > +     else if (!strcasecmp(value, "keep"))
>> > +             return EMPTY_KEEP;
>> > +     else if (!strcasecmp(value, "ask"))
>> > +             return EMPTY_ASK;
>>
>> Not an error but just silently ignored?
>
> Oops, I'll switch it to an error.

Not necessarily (see "Ahh, OK" below).

>> > +     return EMPTY_UNSPECIFIED;
>> > +}
>> > +
>>
>> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>> > +{
>> > +     struct rebase_options *options = opt->value;
>> > +     long value = parse_empty_value(arg);
>>
>> Ahh, OK.
>>
>> Wouldn't it be better to make the variable and the parsing helper
>> function of type "enum empty_type", not "long", just like the field
>> in the rebase_options struct?
>
> Indeed, I'll fix this up.

Thanks. I was wondering if there is something subtle I didn't see
going on around here.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends
  2019-12-20 17:09 ` [PATCH 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2019-12-22  5:11   ` Denton Liu
  2019-12-23 17:17     ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Denton Liu @ 2019-12-22  5:11 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, gitster, plroskin, Elijah Newren

Hi Elijah,

On Fri, Dec 20, 2019 at 05:09:39PM +0000, Elijah Newren via GitGitGadget wrote:
> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
> index 92f95b57da..3879a43fa0 100755
> --- a/t/t3432-rebase-fast-forward.sh
> +++ b/t/t3432-rebase-fast-forward.sh
> @@ -44,19 +44,16 @@ test_rebase_same_head_ () {
>  	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
>  		oldhead=\$(git rev-parse HEAD) &&
>  		test_when_finished 'git reset --hard \$oldhead' &&
> +		cp .git/logs/HEAD expect &&
>  		git rebase$flag $* >stdout &&
>  		if test $what = work
>  		then
> -			# Must check this case first, for 'is up to
> -			# date, rebase forced[...]rewinding head' cases
> -			test_i18ngrep 'rewinding head' stdout
> +			wc -l .git/logs/HEAD >old &&
> +			wc -l .git/logs/HEAD >new &&

Are the above two lines some yet-to-be cleaned up leftovers? I can't see
where `old` and `new` are being used elsewhere.

> +			test_line_count '-gt' $(($old + 2)) .git/logs/HEAD
>  		elif test $what = noop
>  		then
> -			test_i18ngrep 'is up to date' stdout &&
> -			test_i18ngrep ! 'rebase forced' stdout
> -		elif test $what = noop-force
> -		then
> -			test_i18ngrep 'is up to date, rebase forced' stdout
> +			test_cmp expect .git/logs/HEAD
>  		fi &&
>  		newhead=\$(git rev-parse HEAD) &&
>  		if test $cmp = same

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends
  2019-12-22  5:11   ` Denton Liu
@ 2019-12-23 17:17     ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2019-12-23 17:17 UTC (permalink / raw)
  To: Denton Liu
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Junio C Hamano, Pavel Roskin

On Sat, Dec 21, 2019 at 9:11 PM Denton Liu <liu.denton@gmail.com> wrote:
>
> Hi Elijah,
>
> On Fri, Dec 20, 2019 at 05:09:39PM +0000, Elijah Newren via GitGitGadget wrote:
> > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
> > index 92f95b57da..3879a43fa0 100755
> > --- a/t/t3432-rebase-fast-forward.sh
> > +++ b/t/t3432-rebase-fast-forward.sh
> > @@ -44,19 +44,16 @@ test_rebase_same_head_ () {
> >       test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
> >               oldhead=\$(git rev-parse HEAD) &&
> >               test_when_finished 'git reset --hard \$oldhead' &&
> > +             cp .git/logs/HEAD expect &&
> >               git rebase$flag $* >stdout &&
> >               if test $what = work
> >               then
> > -                     # Must check this case first, for 'is up to
> > -                     # date, rebase forced[...]rewinding head' cases
> > -                     test_i18ngrep 'rewinding head' stdout
> > +                     wc -l .git/logs/HEAD >old &&
> > +                     wc -l .git/logs/HEAD >new &&
>
> Are the above two lines some yet-to-be cleaned up leftovers? I can't see
> where `old` and `new` are being used elsewhere.

Um, this whole section is broken and causes it to pass the test for
stupid reasons.  Not sure how I bungled that so badly.  I'll send a
re-roll with a corrected 6/15.

>
> > +                     test_line_count '-gt' $(($old + 2)) .git/logs/HEAD
> >               elif test $what = noop
> >               then
> > -                     test_i18ngrep 'is up to date' stdout &&
> > -                     test_i18ngrep ! 'rebase forced' stdout
> > -             elif test $what = noop-force
> > -             then
> > -                     test_i18ngrep 'is up to date, rebase forced' stdout
> > +                     test_cmp expect .git/logs/HEAD
> >               fi &&
> >               newhead=\$(git rev-parse HEAD) &&
> >               if test $cmp = same

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v2 00/15] rebase: make the default backend configurable
  2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                   ` (15 preceding siblings ...)
  2019-12-20 18:51 ` [PATCH 00/15] rebase: make the default backend configurable Alban Gruin
@ 2019-12-23 18:49 ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
                     ` (15 more replies)
  16 siblings, 16 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano

This series does a lot of work around making the default rebase backend
configurable, and switching the default from the am backend to the
merge/interactive one.

See the cover letter for v1[*] for a more detailed motivation for the
series, the type of changes being made, and areas I'd like to reviewers to
focus on.

Changes since v1:

 * Made some cleanups to the empty type parsing, as suggested by Junio
   (Patch 1)
 * Changed the shell prompt for all rebases to just be "REBASE", as
   suggested by SZEDER (Patch 11)
 * Correctly check that work was done (by checking that reflog grew, patch
   6); flagged by Denton

[*] 
https://lore.kernel.org/git/pull.679.git.git.1576861788.gitgitgadget@gmail.com/

Elijah Newren (15):
  rebase: extend the options for handling of empty commits
  t3406: simplify an already simple test
  rebase, sequencer: remove the broken GIT_QUIET handling
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase: fix handling of restrict_revision
  t3432: make these tests work with either am or merge backends
  rebase: allow more types of rebases to fast-forward
  git-rebase.txt: add more details about behavioral differences of
    backends
  rebase: move incompatibility checks between backend options a bit
    earlier
  rebase: add an --am option
  contrib: change the prompt for interactive-based rebases
  rebase tests: mark tests specific to the am-backend with --am
  rebase tests: repeat some tests using the merge backend instead of am
  rebase: make the backend configurable via config setting
  rebase: change the default backend from "am" to "merge"

 Documentation/config/rebase.txt         |   8 ++
 Documentation/git-rebase.txt            | 150 ++++++++++++++++----
 builtin/rebase.c                        | 177 +++++++++++++++++++-----
 contrib/completion/git-prompt.sh        |   4 +-
 rebase-interactive.c                    |   4 +-
 rebase-interactive.h                    |   2 +-
 sequencer.c                             |  80 ++++++++---
 sequencer.h                             |   6 +-
 t/t3400-rebase.sh                       |  36 ++++-
 t/t3401-rebase-and-am-rename.sh         |   4 +-
 t/t3404-rebase-interactive.sh           |   2 +-
 t/t3406-rebase-message.sh               |  19 ++-
 t/t3407-rebase-abort.sh                 |   6 +-
 t/t3420-rebase-autostash.sh             |   2 +-
 t/t3421-rebase-topology-linear.sh       |   4 +-
 t/t3424-rebase-empty.sh                 |  89 ++++++++++++
 t/t3425-rebase-topology-merges.sh       |   8 +-
 t/t3427-rebase-subtree.sh               |  16 ++-
 t/t3432-rebase-fast-forward.sh          |  54 ++++----
 t/t3433-rebase-options-compatibility.sh |  13 +-
 t/t5407-post-rewrite-hook.sh            |  12 +-
 t/t5520-pull.sh                         |  27 +++-
 t/t6047-diff3-conflict-markers.sh       |  13 +-
 t/t7512-status-help.sh                  |  12 +-
 t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-
 t/t9903-bash-prompt.sh                  |   8 +-
 26 files changed, 577 insertions(+), 182 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh


base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v2
Pull-Request: https://github.com/git/git/pull/679

Range-diff vs v1:

  1:  13e2056e78 !  1:  1c2b77e94d rebase: extend the options for handling of empty commits
     @@ -219,28 +219,24 @@
       	return 0;
       }
       
     -+static long parse_empty_value(const char *value)
     ++static enum empty_type parse_empty_value(const char *value)
      +{
     -+	if (!value)
     -+		return EMPTY_UNSPECIFIED;
     -+	else if (!strcasecmp(value, "drop"))
     ++	if (!strcasecmp(value, "drop"))
      +		return EMPTY_DROP;
      +	else if (!strcasecmp(value, "keep"))
      +		return EMPTY_KEEP;
      +	else if (!strcasecmp(value, "ask"))
      +		return EMPTY_ASK;
     -+	return EMPTY_UNSPECIFIED;
     ++
     ++	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
      +}
      +
      +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
      +{
      +	struct rebase_options *options = opt->value;
     -+	long value = parse_empty_value(arg);
     ++	enum empty_type value = parse_empty_value(arg);
      +
      +	BUG_ON_OPT_NEG(unset);
     -+	if (value < 0)
     -+		return error(_("option empty accepts \"drop\", "
     -+			       "\"keep\", and \"ask\""));
      +
      +	options->empty = value;
      +	return 0;
  2:  47ea99fb30 =  2:  bd3c5ec155 t3406: simplify an already simple test
  3:  b99feebae1 =  3:  49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling
  4:  66bee10d1c =  4:  478479358f rebase: make sure to pass along the quiet flag to the sequencer
  5:  b84faa8684 =  5:  ee26f5a161 rebase: fix handling of restrict_revision
  6:  9b1ad46c58 !  6:  34a69def33 t3432: make these tests work with either am or merge backends
     @@ -37,9 +37,8 @@
      -			# Must check this case first, for 'is up to
      -			# date, rebase forced[...]rewinding head' cases
      -			test_i18ngrep 'rewinding head' stdout
     -+			wc -l .git/logs/HEAD >old &&
     -+			wc -l .git/logs/HEAD >new &&
     -+			test_line_count '-gt' $(($old + 2)) .git/logs/HEAD
     ++			old=\$(wc -l <expect) &&
     ++			test_line_count '-gt' \$old .git/logs/HEAD
       		elif test $what = noop
       		then
      -			test_i18ngrep 'is up to date' stdout &&
     @@ -108,10 +107,3 @@
       test_rebase_same_head success noop same success work diff --onto master... master
       test_rebase_same_head success noop same success work diff --keep-base master
       test_rebase_same_head success noop same success work diff --keep-base
     --test_rebase_same_head failure work same success work diff --fork-point --onto B B
     --test_rebase_same_head failure work same success work diff --fork-point --onto B... B
     -+test_rebase_same_head success work same success work diff --fork-point --onto B B
     -+test_rebase_same_head success work same success work diff --fork-point --onto B... B
     - test_rebase_same_head success noop same success work diff --fork-point --onto master... master
     - test_rebase_same_head success noop same success work diff --fork-point --keep-base master
     - 
  7:  a4073df910 =  7:  f2c92853b4 rebase: allow more types of rebases to fast-forward
  8:  90e8927ea0 =  8:  b307340f7c git-rebase.txt: add more details about behavioral differences of backends
  9:  5c31d664f3 =  9:  7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier
 10:  340bf71c5c = 10:  1df11f0b51 rebase: add an --am option
 11:  1c3f8ba328 ! 11:  94b5a3051d contrib: change the prompt for am-based rebases
     @@ -1,17 +1,38 @@
      Author: Elijah Newren <newren@gmail.com>
      
     -    contrib: change the prompt for am-based rebases
     -
     -    The prompt for am-based rebases was REBASE, while for interactive-based
     -    rebases was REBASE-i.  A while ago, we switched merge-based rebases from
     -    using REBASE-m to REBASE-i via re-implementing the merge backend based
     -    on the interactive backend.  We will soon be changing the default rebase
     -    backend to the interactive one, meaning the default prompt will be
     -    REBASE-i rather than REBASE.  We have also noted in the documentation
     -    that currently am-specific options will be implemented in the
     -    interactive backend, and even the --am flag may eventually imply an
     -    interactive-based rebase.  As such, change the prompt for an am-based
     -    rebase from REBASE to REBASE-a.
     +    contrib: change the prompt for interactive-based rebases
     +
     +    In the past, we had different prompts for different types of rebases:
     +       REBASE: for am-based rebases
     +       REBASE-m: for merge-based rebases
     +       REBASE-i: for interactive-based rebases
     +
     +    It's not clear why this distinction was necessary or helpful; when the
     +    prompt was added in commit e75201963f67 ("Improve bash prompt to detect
     +    various states like an unfinished merge", 2007-09-30), it simply added
     +    these three different types.  Perhaps there was a useful purpose back
     +    then, but there have been some changes:
     +
     +      * The merge backend was deleted after being implemented on top of the
     +        interactive backend, causing the prompt for merge-based rebases to
     +        change from REBASE-m to REBASE-i.
     +      * The interactive backend is used for multiple different types of
     +        non-interactive rebases, so the "-i" part of the prompt doesn't
     +        really mean what it used to.
     +      * Rebase backends have gained more abilities and have a great deal of
     +        overlap, sometimes making it hard to distinguish them.
     +      * Behavioral differences between the backends have also been ironed
     +        out.
     +      * We want to change the default backend from am to interactive, which
     +        means people would get "REBASE-i" by default if we didn't change
     +        the prompt, and only if they specified --am or --whitespace or -C
     +        would they get the "REBASE" prompt.
     +      * In the future, we plan to have "--whitespace", "-C", and even "--am"
     +        run the interactive backend once it can handle everything the
     +        am-backend can.
     +
     +    For all these reasons, make the prompt for any type of rebase just be
     +    "REBASE".
      
          Signed-off-by: Elijah Newren <newren@gmail.com>
      
     @@ -19,26 +40,46 @@
       --- a/contrib/completion/git-prompt.sh
       +++ b/contrib/completion/git-prompt.sh
      @@
     - 			__git_eread "$g/rebase-apply/last" total
     - 			if [ -f "$g/rebase-apply/rebasing" ]; then
     - 				__git_eread "$g/rebase-apply/head-name" b
     --				r="|REBASE"
     -+				r="|REBASE-a"
     - 			elif [ -f "$g/rebase-apply/applying" ]; then
     - 				r="|AM"
     - 			else
     + 		__git_eread "$g/rebase-merge/msgnum" step
     + 		__git_eread "$g/rebase-merge/end" total
     + 		if [ -f "$g/rebase-merge/interactive" ]; then
     +-			r="|REBASE-i"
     ++			r="|REBASE"
     + 		else
     +-			r="|REBASE-m"
     ++			r="|REBASE"
     + 		fi
     + 	else
     + 		if [ -d "$g/rebase-apply" ]; then
      
       diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
       --- a/t/t9903-bash-prompt.sh
       +++ b/t/t9903-bash-prompt.sh
     +@@
     + '
     + 
     + test_expect_success 'prompt - interactive rebase' '
     +-	printf " (b1|REBASE-i 2/3)" >expected &&
     ++	printf " (b1|REBASE 2/3)" >expected &&
     + 	write_script fake_editor.sh <<-\EOF &&
     + 		echo "exec echo" >"$1"
     + 		echo "edit $(git log -1 --format="%h")" >>"$1"
     +@@
     + '
     + 
     + test_expect_success 'prompt - rebase merge' '
     +-	printf " (b2|REBASE-i 1/3)" >expected &&
     ++	printf " (b2|REBASE 1/3)" >expected &&
     + 	git checkout b2 &&
     + 	test_when_finished "git checkout master" &&
     + 	test_must_fail git rebase --merge b1 b2 &&
      @@
       	test_cmp expected "$actual"
       '
       
      -test_expect_success 'prompt - rebase' '
     --	printf " (b2|REBASE 1/3)" >expected &&
      +test_expect_success 'prompt - rebase am' '
     -+	printf " (b2|REBASE-a 1/3)" >expected &&
     + 	printf " (b2|REBASE 1/3)" >expected &&
       	git checkout b2 &&
       	test_when_finished "git checkout master" &&
      -	test_must_fail git rebase b1 b2 &&
 12:  bcac3b77e8 = 12:  c905d288bf rebase tests: mark tests specific to the am-backend with --am
 13:  81d0f18b3e = 13:  0287881361 rebase tests: repeat some tests using the merge backend instead of am
 14:  04db4472d3 = 14:  ec782e711c rebase: make the backend configurable via config setting
 15:  999587933b = 15:  7adcbc0bc5 rebase: change the default backend from "am" to "merge"

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v2 01/15] rebase: extend the options for handling of empty commits
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
                     ` (14 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Extend the interactive machinery with the ability to handle the full
spread of options for how to handle commits that either start or become
empty (by "become empty" I mean the changes in a commit are a subset of
changes that exist upstream, so the net effect of applying the commit is
no changes).  Introduce a new command line flag for selecting the
desired behavior:
    --empty={drop,keep,ask}
with the definitions:
    drop: drop empty commits
    keep: keep empty commits
    ask:  provide the user a chance to interact and pick what to do with
          empty commits on a case-by-case basis

Note that traditionally, am-based rebases have always dropped commits
that either started or became empty, while interactive-based rebases
have defaulted to ask (and provided an option to keep commits that
started empty).  This difference made sense since users of an am-based
rebase just wanted to quickly batch apply a sequence of commits, while
users editing a todo list will likely want the chance to interact and
handle unusual cases on a case-by-case basis.  However, not all rebases
using the interactive machinery are explicitly interactive anymore.  In
particular --merge was always meant to behave more like --am: just
rebase a batch of commits without popping up a todo list.

If the --empty flag is not specified, pick defaults as follows:
    explicitly interactive: ask
    --exec: keep (exec is about checking existing commits, and often
                  used without actually changing the base.  Thus the
                  expectation is that the user doesn't necessarily want
                  anything to change; they just want to test).
    otherwise: drop

Also, this commit makes --keep-empty just imply --empty=keep, and hides
it from help so that we aren't confusing users with different ways to do
the same thing.  (I could have added a --drop-empty flag, but then that
invites users to specify both --keep-empty and --drop-empty and we have
to add sanity checking around that; it seems cleaner to have a single
multi-valued option.)  This actually fixes --keep-empty too; previously,
it only meant to sometimes keep empty commits, in particular commits
which started empty would be kept.  But it would still error out and ask
the user what to do with commits that became empty.  Now it keeps empty
commits, as instructed.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt      | 35 ++++++------
 builtin/rebase.c                  | 83 +++++++++++++++++++++++++---
 rebase-interactive.c              |  4 +-
 rebase-interactive.h              |  2 +-
 sequencer.c                       | 74 +++++++++++++++++++------
 sequencer.h                       |  6 ++-
 t/t3421-rebase-topology-linear.sh |  4 +-
 t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++
 t/t3427-rebase-subtree.sh         | 16 +++---
 9 files changed, 263 insertions(+), 50 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d0e2d27cc..ff32ca1080 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--empty={drop,keep,ask}::
+	How to handle commits that become empty (because they contain a
+	subset of already upstream changes) or start empty.  With drop
+	(the default), commits that start or become empty are dropped.
+	With keep (implied by --exec), such commits are kept.  With ask
+	(implied by --interactive), the rebase will halt when an empty
+	commit is applied allowing you to choose whether to drop it or
+	commit it.  Also with ask, if the rebase is interactive then
+	commits which start empty will be commented out in the todo
+	action list (giving you a chance to uncomment).
++
+Note that this has no effect on commits which are already upstream (as
+can be checked via `git log --cherry-mark ...`), which are always
+dropped by rebase.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --keep-empty::
-	Keep the commits that do not change anything from its
-	parents in the result.
+	Deprecated alias for what is now known as --empty=keep.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -569,6 +585,7 @@ are incompatible with the following options:
  * --interactive
  * --exec
  * --keep-empty
+ * --empty=
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --ignore-whitespace
  * --preserve-merges and --committer-date-is-author-date
  * --preserve-merges and --ignore-date
+ * --preserve-merges and --empty=
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES
 
 There are some subtle differences how the backends behave.
 
-Empty commits
-~~~~~~~~~~~~~
-
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
-
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
-
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ddf33bc9d4..6903249307 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -50,8 +50,16 @@ enum rebase_type {
 	REBASE_PRESERVE_MERGES
 };
 
+enum empty_type {
+	EMPTY_UNSPECIFIED = -1,
+	EMPTY_DROP,
+	EMPTY_KEEP,
+	EMPTY_ASK
+};
+
 struct rebase_options {
 	enum rebase_type type;
+	enum empty_type empty;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -77,7 +85,6 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
-	int keep_empty;
 	int autosquash;
 	int ignore_whitespace;
 	char *gpg_sign_opt;
@@ -95,6 +102,7 @@ struct rebase_options {
 
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
+		.empty = EMPTY_UNSPECIFIED,	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
 	replay.allow_empty = 1;
 	replay.allow_empty_message = opts->allow_empty_message;
+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
+					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,
 
 	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
-	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_ASK &&
+		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
+			TODO_LIST_ASK_EMPTY : 0;
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,
 	return ret;
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+				int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->empty = EMPTY_KEEP;
+	opts->type = REBASE_INTERACTIVE;
+	return 0;
+}
+
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
-		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
 			 N_("allow commits with empty messages")),
 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		opts->allow_rerere_autoupdate ?
 			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
 			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
+	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
 	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static enum empty_type parse_empty_value(const char *value)
+{
+	if (!strcasecmp(value, "drop"))
+		return EMPTY_DROP;
+	else if (!strcasecmp(value, "keep"))
+		return EMPTY_KEEP;
+	else if (!strcasecmp(value, "ask"))
+		return EMPTY_ASK;
+
+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+	enum empty_type value = parse_empty_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+
+	options->empty = value;
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_BOOL('k', "keep-empty", &options.keep_empty,
-			 N_("preserve empty commits during rebase")),
+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
+			       N_("how to handle empty commits"),
+			       PARSE_OPT_NONEG, parse_opt_empty),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
-	if (options.keep_empty)
-		imply_interactive(&options, "--keep-empty");
+	if (options.empty != EMPTY_UNSPECIFIED)
+		imply_interactive(&options, "--empty");
 
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
@@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
+	if (options.empty == EMPTY_UNSPECIFIED) {
+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+			options.empty = EMPTY_ASK;
+		else if (exec.nr > 0)
+			options.empty = EMPTY_KEEP;
+		else
+			options.empty = EMPTY_DROP;
+	}
 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
diff --git a/rebase-interactive.c b/rebase-interactive.c
index aa18ae82b7..ad82bf77df 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
 	return MISSING_COMMIT_CHECK_IGNORE;
 }
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf)
 {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,
 
 	strbuf_add_commented_lines(buf, msg, strlen(msg));
 
-	if (!keep_empty) {
+	if (!no_ask_empty) {
 		msg = _("Note that empty commits are commented out");
 		strbuf_add_commented_lines(buf, msg, strlen(msg));
 	}
diff --git a/rebase-interactive.h b/rebase-interactive.h
index 44dbb06311..f531e00ba7 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -5,7 +5,7 @@ struct strbuf;
 struct repository;
 struct todo_list;
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf);
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/sequencer.c b/sequencer.c
index 763ccbbc45..d2c11f34b7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
 static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
 
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,
 	empty_commit = is_original_commit_empty(commit);
 	if (empty_commit < 0)
 		return empty_commit;
-	if (!empty_commit)
+	if (!empty_commit || opts->ask_on_initially_empty)
 		return 0;
 	else
 		return 1;
@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,
 	char *author = NULL;
 	struct commit_message msg = { NULL, NULL, NULL, NULL };
 	struct strbuf msgbuf = STRBUF_INIT;
-	int res, unborn = 0, reword = 0, allow;
+	int res, unborn = 0, reword = 0, allow, drop_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,
 		goto leave;
 	}
 
-	allow = allow_empty(r, opts, commit);
-	if (allow < 0) {
-		res = allow;
-		goto leave;
-	} else if (allow)
-		flags |= ALLOW_EMPTY;
-	if (!opts->no_commit) {
+	drop_commit = 0;
+	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
+		drop_commit = 1;
+		fprintf(stderr, _("No changes -- Patch already applied."));
+	} else {
+		allow = allow_empty(r, opts, commit);
+		if (allow < 0) {
+			res = allow;
+			goto leave;
+		} else if (allow) {
+			flags |= ALLOW_EMPTY;
+		}
+	}
+	if (!opts->no_commit && !drop_commit) {
 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 			res = do_commit(r, msg_file, author, opts, flags);
 		else
@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
 	else if (!strcmp(key, "options.allow-empty-message"))
 		opts->allow_empty_message =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.drop-redundant-commits"))
+		opts->drop_redundant_commits =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.keep-redundant-commits"))
 		opts->keep_redundant_commits =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.ask_on_initially_empty"))
+		opts->ask_on_initially_empty =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.signoff"))
 		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.record-origin"))
@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
+		if (file_exists(rebase_path_drop_redundant_commits()))
+			opts->drop_redundant_commits = 1;
+
+		if (file_exists(rebase_path_keep_redundant_commits()))
+			opts->keep_redundant_commits = 1;
+
+		if (file_exists(rebase_path_ask_on_initially_empty()))
+			opts->ask_on_initially_empty = 1;
+
 		read_strategy_opts(opts, &buf);
 		strbuf_release(&buf);
 
@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_cdate_is_adate(), "%s", "");
 	if (opts->ignore_date)
 		write_file(rebase_path_ignore_date(), "%s", "");
+	if (opts->drop_redundant_commits)
+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
+	if (opts->keep_redundant_commits)
+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
+	if (opts->ask_on_initially_empty)
+		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
 	if (opts->allow_empty_message)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.allow-empty-message", "true");
+	if (opts->drop_redundant_commits)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.drop-redundant-commits", "true");
 	if (opts->keep_redundant_commits)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.keep-redundant-commits", "true");
+	if (opts->ask_on_initially_empty)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.ask_on_initially_empty", "true");
 	if (opts->signoff)
 		res |= git_config_set_in_file_gently(opts_file,
 					"options.signoff", "true");
@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 				   struct rev_info *revs, struct strbuf *out,
 				   unsigned flags)
 {
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
 	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		is_empty = is_original_commit_empty(commit);
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
+		if (is_empty && drop_empty)
+			continue;
 
 		strbuf_reset(&oneline);
 		pretty_print_commit(pp, commit, &oneline);
@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		if (!to_merge) {
 			/* non-merge commit: easy case */
 			strbuf_reset(&buf);
-			if (!keep_empty && is_empty)
+			if (is_empty && ask_empty)
 				strbuf_addf(&buf, "%c ", comment_line_char);
 			strbuf_addf(&buf, "%s %s %s", cmd_pick,
 				    oid_to_hex(&commit->object.oid),
@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	struct pretty_print_context pp = {0};
 	struct rev_info revs;
 	struct commit *commit;
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 		return make_script_with_merges(&pp, &revs, out, flags);
 
 	while ((commit = get_revision(&revs))) {
-		int is_empty  = is_original_commit_empty(commit);
+		int is_empty = is_original_commit_empty(commit);
 
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
-		if (!keep_empty && is_empty)
+		if (is_empty && drop_empty)
+			continue;
+		if (is_empty && ask_empty)
 			strbuf_addf(out, "%c ", comment_line_char);
 		strbuf_addf(out, "%s %s ", insn,
 			    oid_to_hex(&commit->object.oid));
@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
 
 	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
 	if (flags & TODO_LIST_APPEND_TODO_HELP)
-		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
+				 count_commands(todo_list),
 				 shortrevisions, shortonto, &buf);
 
 	res = write_message(buf.buf, buf.len, file, 0);
diff --git a/sequencer.h b/sequencer.h
index e9a0e03ea2..1c3abb661c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -39,7 +39,9 @@ struct replay_opts {
 	int allow_rerere_auto;
 	int allow_empty;
 	int allow_empty_message;
+	int drop_redundant_commits;
 	int keep_redundant_commits;
+	int ask_on_initially_empty;
 	int verbose;
 	int quiet;
 	int reschedule_failed_exec;
@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-#define TODO_LIST_KEEP_EMPTY (1U << 0)
+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);
  * `--onto`, we do not want to re-generate the root commits.
  */
 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
+#define TODO_LIST_DROP_EMPTY (1U << 7)
+#define TODO_LIST_ASK_EMPTY (1U << 8)
 
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 325072b0a3..d23e0bf778 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -230,7 +230,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
new file mode 100755
index 0000000000..9d52e1417f
--- /dev/null
+++ b/t/t3424-rebase-empty.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_success 'rebase --merge --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	test_must_fail git rebase --skip &&
+	git commit --allow-empty &&
+	git rebase --continue &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
+
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+
+test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index bec48e6a1f..468ebc1bef 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 02/15] t3406: simplify an already simple test
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
                     ` (13 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

When the merge backend was re-implemented on top of the interactive
backend, the output of rebase --merge changed a little.  This change
allowed this test to be simplified, though it wasn't noticed until now.
Simplify the testcase a little.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3406-rebase-message.sh | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index b393e1e9fe..0c2c569f95 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -18,11 +18,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 03/15] rebase, sequencer: remove the broken GIT_QUIET handling
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
                     ` (12 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The GIT_QUIET environment variable was used to signal the non-am
backends that the rebase should perform quietly.  The preserve-merges
backend does not make use of the quiet flag anywhere (other than to
write out its state whenever it writes state), and this mechanism was
broken in the conversion from shell to C.  Since this environment
variable was specifically designed for scripts and the only backend that
would still use it is no longer a script, just gut this code.

A subsequent commit will fix --quiet for the interactive/merge backend
in a different way.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 6 ++----
 sequencer.c      | 6 ++----
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6903249307..32026a62e8 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 	write_file(state_dir_path("orig-head", opts), "%s",
 		   oid_to_hex(&opts->orig_head));
-	write_file(state_dir_path("quiet", opts), "%s",
-		   opts->flags & REBASE_NO_QUIET ? "" : "t");
+	if (!(opts->flags & REBASE_NO_QUIET))
+		write_file(state_dir_path("quiet", opts), "%s", "");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
 	add_var(&script_snippet, "git_am_opt", buf.buf);
 	strbuf_release(&buf);
diff --git a/sequencer.c b/sequencer.c
index d2c11f34b7..71062212a5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const char *orig_head)
 {
-	const char *quiet = getenv("GIT_QUIET");
-
 	if (head_name)
 		write_file(rebase_path_head_name(), "%s\n", head_name);
 	if (onto)
@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 	if (orig_head)
 		write_file(rebase_path_orig_head(), "%s\n", orig_head);
 
-	if (quiet)
-		write_file(rebase_path_quiet(), "%s\n", quiet);
+	if (opts->quiet)
+		write_file(rebase_path_quiet(), "%s", "");
 	if (opts->verbose)
 		write_file(rebase_path_verbose(), "%s", "");
 	if (opts->strategy)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 04/15] rebase: make sure to pass along the quiet flag to the sequencer
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (2 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
                     ` (11 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  | 3 ++-
 t/t3400-rebase.sh | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 32026a62e8..5014c9a437 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
 	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
 					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
+	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -1502,7 +1503,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			 N_("allow pre-rebase hook to run")),
 		OPT_NEGBIT('q', "quiet", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+			   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
 		OPT_BIT('v', "verbose", &options.flags,
 			N_("display a diffstat of what changed upstream"),
 			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 221b35f2df..79762b989a 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
 	git rebase -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	(
 		echo "One" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 05/15] rebase: fix handling of restrict_revision
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (3 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
                     ` (10 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

restrict_revision in the original shell script was an excluded revision
range.  It is also treated that way by the am-backend.  In the
conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the
interactive-backend accidentally treated it as a positive revision
rather than a negated one.

This was missed as there were no tests in the testsuite that tested an
interactive rebase with fork-point behavior.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  |  4 ++--
 t/t3400-rebase.sh | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 5014c9a437..f1de5c8186 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 
 	argv_array_pushl(&make_script_args, "", revisions, NULL);
 	if (opts->restrict_revision)
-		argv_array_push(&make_script_args,
-				oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_pushf(&make_script_args, "^%s",
+				 oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
 				    make_script_args.argc, make_script_args.argv,
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 79762b989a..71fd6396cd 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
 	git rebase master
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
 	git rebase &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 06/15] t3432: make these tests work with either am or merge backends
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (4 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
                     ` (9 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3432 had several stress tests for can_fast_forward(), whose intent was
to ensure we were using the optimization of just fast forwarding when
possible.  However, these tests verified that fast forwards had happened
based on the output that rebase printed to the terminal.  We can instead
test more directly that we actually fast-forwarded by checking the
reflog, which also has the side effect of making the tests applicable
for the merge/interactive backend.

This change does lose the distinction between "noop" and "noop-force",
but as stated in commit c9efc216830f ("t3432: test for --no-ff's
interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo,
just test for what we're currently doing.".

This change does not actually run these tests with the merge/interactive
backend; instead this is just a preparatory commit.  A subsequent commit
which fixes can_fast_forward() to work with that backend will then also
change t3432 to add tests of that backend as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 92f95b57da..7432c0e241 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -44,19 +44,15 @@ test_rebase_same_head_ () {
 	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
 		oldhead=\$(git rev-parse HEAD) &&
 		test_when_finished 'git reset --hard \$oldhead' &&
+		cp .git/logs/HEAD expect &&
 		git rebase$flag $* >stdout &&
 		if test $what = work
 		then
-			# Must check this case first, for 'is up to
-			# date, rebase forced[...]rewinding head' cases
-			test_i18ngrep 'rewinding head' stdout
+			old=\$(wc -l <expect) &&
+			test_line_count '-gt' \$old .git/logs/HEAD
 		elif test $what = noop
 		then
-			test_i18ngrep 'is up to date' stdout &&
-			test_i18ngrep ! 'rebase forced' stdout
-		elif test $what = noop-force
-		then
-			test_i18ngrep 'is up to date, rebase forced' stdout
+			test_cmp expect .git/logs/HEAD
 		fi &&
 		newhead=\$(git rev-parse HEAD) &&
 		if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () {
 
 changes='no changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '
 
 changes='our changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '
 '
 
 changes='our and their changes'
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
 test_rebase_same_head success noop same success work diff --onto master... master
 test_rebase_same_head success noop same success work diff --keep-base master
 test_rebase_same_head success noop same success work diff --keep-base
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 07/15] rebase: allow more types of rebases to fast-forward
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (5 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
                     ` (8 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we dis-allowed rebases using the interactive backend from
performing a fast-forward to short-circuit the rebase operation.  This
made sense for explicitly interactive rebases and some implicitly
interactive rebases, but certainly became overly stringent when the
merge backend was re-implemented via the interactive backend.

Just as the am-based rebase has always had to disable the fast-forward
based on a variety of conditions or flags (e.g. --signoff, --whitespace,
etc.), we need to do the same but now with a few more options.  However,
continuing to use REBASE_FORCE for tracking this is problematic because
the interactive backend used it for a different purpose.  (When
REBASE_FORCE wasn't set, the interactive backend would not fast-forward
the whole series but would fast-forward individual "pick" commits at the
beginning of the todo list, and then a squash or something would cause
it to start generating new commits.)  So, introduce a new
allow_preemptive_ff flag contained within cmd_rebase() and use it to
track whether we are going to allow a pre-emptive fast-forward that
short-circuits the whole rebase.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c               | 18 ++++++++++++++----
 t/t3432-rebase-fast-forward.sh |  2 ++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index f1de5c8186..7027e34567 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1493,6 +1493,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
+	int allow_preemptive_ff = 1;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	    options.ignore_date)
 		options.flags |= REBASE_FORCE;
 
+	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+	    (action != ACTION_NONE) ||
+	    (exec.nr > 0) ||
+	    options.autosquash) {
+		allow_preemptive_ff = 0;
+	}
+
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
 		if (!strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
-			options.flags |= REBASE_FORCE;
+			allow_preemptive_ff = 0;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -2144,12 +2152,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	/*
 	 * Check if we are already based on onto with linear history,
 	 * in which case we could fast-forward without replacing the commits
-	 * with new commits recreated by replaying their changes. This
-	 * optimization must not be done if this is an interactive rebase.
+	 * with new commits recreated by replaying their changes.
+	 *
+	 * Note that can_fast_forward() initializes merge_base, so we have to
+	 * call it before checking allow_preemptive_ff.
 	 */
 	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
 		    &options.orig_head, &merge_base) &&
-	    !is_interactive(&options)) {
+	    allow_preemptive_ff) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 7432c0e241..40388ccf9f 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -30,6 +30,8 @@ test_rebase_same_head () {
 	shift &&
 	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 08/15] git-rebase.txt: add more details about behavioral differences of backends
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (6 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
                     ` (7 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---
 t/t3433-rebase-options-compatibility.sh |   5 +-
 2 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index ff32ca1080..f1ace07c38 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used
 with `--keep-base` in order to drop those commits from your branch.
 
 --ignore-whitespace::
-	Behaves differently depending on which backend is selected.
-+
-'am' backend: When applying a patch, ignore changes in whitespace in
-context lines if necessary.
-+
-'interactive' backend: Treat lines with only whitespace changes as
-unchanged for the sake of a three-way merge.
+	Ignore whitespace-only changes in the commits being rebased,
+	which may avoid "unnecessary" conflicts.  (Both backends
+	currently have differing edgecase bugs with this option; see
+	BEHAVIORAL DIFFERENCES.)
 
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Directory rename heuristics are enabled in the merge and interactive
-backends.  Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the am backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge and interactive
+backends to provide you warnings in such cases.
+
+Context
+~~~~~~~
+
+The am backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally).  Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes.  The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file.  The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines.  However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked.  There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The interactive backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from.  Since the am backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary.  Also, when merge.conflictStyle is
+set to diff3, the am backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The interactive backend works with the full commits on both sides of
+history and thus has no such limitations.
+
+--ignore-whitespace
+~~~~~~~~~~~~~~~~~~~
+
+The --ignore-whitespace option is supposed to ignore whitespace-only
+changes if it allows the code to merge cleanly.  Unfortunately, the
+different backends implement this differently, and both have different
+edge case bugs.
++
+'am' backend: When applying a patch, ignore changes in whitespace in
+context lines if necessary.  (Which implies that if the whitespace
+change was not in the context lines but on a line with a real change,
+then the rebase will still fail with "unnecessary" content conflicts.)
++
+'interactive' backend: Treat lines with only whitespace changes as
+unchanged for the sake of a three-way merge.  This means that if one
+side made no changes and the commits being rebased had whitespace-only
+changes, those whitespaces fixups will be discarded despite the fact
+that they present no content conflict.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+  the changes made in the reflog, though both will make use of the
+  word "rebase".
+
+* Progress, informational, and error messages: The two backends
+  provide slightly different progress and informational messages.
+  Also, the am backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the interactive backend
+  writes them to stderr.
+
+* State directories: The two backends keep their state in different
+  directories under .git/
 
 include::merge-strategies.txt[]
 
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index 5166f158dd..bd4d2d2f63 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe
 GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
 export GIT_AUTHOR_DATE
 
-# This is a special case in which both am and interactive backends
-# provide the same output. It was done intentionally because
-# both the backends fall short of optimal behaviour.
+# This is a common case in which both am and interactive backends
+# provide the same output with --ignore-whitespace.
 test_expect_success 'setup' '
 	git checkout -b topic &&
 	q_to_tab >file <<-\EOF &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 09/15] rebase: move incompatibility checks between backend options a bit earlier
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (7 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
                     ` (6 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 7027e34567..d2b99e9908 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1906,6 +1906,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
+	if (options.git_am_opts.argc) {
+		/* all am options except -q are compatible only with --am */
+		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+			if (strcmp(options.git_am_opts.argv[i], "-q"))
+				break;
+
+		if (is_interactive(&options) && i >= 0)
+			die(_("cannot combine am options with either "
+			      "interactive or merge options"));
+	}
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
@@ -1936,17 +1947,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (reschedule_failed_exec >= 0)
 		options.reschedule_failed_exec = reschedule_failed_exec;
 
-	if (options.git_am_opts.argc) {
-		/* all am options except -q are compatible only with --am */
-		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
-			if (strcmp(options.git_am_opts.argv[i], "-q"))
-				break;
-
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
-	}
-
 	if (options.signoff) {
 		if (options.type == REBASE_PRESERVE_MERGES)
 			die("cannot combine '--signoff' with "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 10/15] rebase: add an --am option
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (8 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
                     ` (5 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Currently, this option doesn't do anything except error out if any
options requiring the interactive-backend are also passed.  However,
when we make the default backend configurable later in this series, this
flag will provide a way to override the config setting.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 11 ++++++++++-
 builtin/rebase.c             | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index f1ace07c38..cf1ac2e359 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--am:
+	Use git-am internally to rebase.  This option may become a
+	no-op in the future once the interactive backend handles
+	everything the am one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --empty={drop,keep,ask}::
 	How to handle commits that become empty (because they contain a
 	subset of already upstream changes) or start empty.  With drop
@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.
+	ever ignored.  Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
+	Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
+ * --am
  * --whitespace
  * -C
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index d2b99e9908..b7915fc0cb 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	return res && is_linear_history(onto, head);
 }
 
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_AM;
+
+	return 0;
+}
+
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
+		{ OPTION_CALLBACK, 0, "am", &options, NULL,
+			N_("use apply-mail strategies to rebase"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
 			N_("use merging strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc) {
+	if (options.git_am_opts.argc || options.type == REBASE_AM) {
 		/* all am options except -q are compatible only with --am */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (9 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 22:00     ` Denton Liu
  2019-12-23 18:49   ` [PATCH v2 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
                     ` (4 subsequent siblings)
  15 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we had different prompts for different types of rebases:
   REBASE: for am-based rebases
   REBASE-m: for merge-based rebases
   REBASE-i: for interactive-based rebases

It's not clear why this distinction was necessary or helpful; when the
prompt was added in commit e75201963f67 ("Improve bash prompt to detect
various states like an unfinished merge", 2007-09-30), it simply added
these three different types.  Perhaps there was a useful purpose back
then, but there have been some changes:

  * The merge backend was deleted after being implemented on top of the
    interactive backend, causing the prompt for merge-based rebases to
    change from REBASE-m to REBASE-i.
  * The interactive backend is used for multiple different types of
    non-interactive rebases, so the "-i" part of the prompt doesn't
    really mean what it used to.
  * Rebase backends have gained more abilities and have a great deal of
    overlap, sometimes making it hard to distinguish them.
  * Behavioral differences between the backends have also been ironed
    out.
  * We want to change the default backend from am to interactive, which
    means people would get "REBASE-i" by default if we didn't change
    the prompt, and only if they specified --am or --whitespace or -C
    would they get the "REBASE" prompt.
  * In the future, we plan to have "--whitespace", "-C", and even "--am"
    run the interactive backend once it can handle everything the
    am-backend can.

For all these reasons, make the prompt for any type of rebase just be
"REBASE".

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 contrib/completion/git-prompt.sh | 4 ++--
 t/t9903-bash-prompt.sh           | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd47b..8f8a22ba60 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -430,9 +430,9 @@ __git_ps1 ()
 		__git_eread "$g/rebase-merge/msgnum" step
 		__git_eread "$g/rebase-merge/end" total
 		if [ -f "$g/rebase-merge/interactive" ]; then
-			r="|REBASE-i"
+			r="|REBASE"
 		else
-			r="|REBASE-m"
+			r="|REBASE"
 		fi
 	else
 		if [ -d "$g/rebase-apply" ]; then
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 88bc733ad6..7ca35d358d 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
 '
 
 test_expect_success 'prompt - interactive rebase' '
-	printf " (b1|REBASE-i 2/3)" >expected &&
+	printf " (b1|REBASE 2/3)" >expected &&
 	write_script fake_editor.sh <<-\EOF &&
 		echo "exec echo" >"$1"
 		echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
 '
 
 test_expect_success 'prompt - rebase merge' '
-	printf " (b2|REBASE-i 1/3)" >expected &&
+	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
 	test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
+test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --am b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 12/15] rebase tests: mark tests specific to the am-backend with --am
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (10 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
                     ` (3 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

We have many rebase tests in the testsuite, and often the same test is
repeated multiple times just testing different backends.  For those
tests that were specifically trying to test the am backend, add the --am
flag.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3400-rebase.sh                       | 10 +++++-----
 t/t3401-rebase-and-am-rename.sh         |  4 ++--
 t/t3404-rebase-interactive.sh           |  2 +-
 t/t3406-rebase-message.sh               | 12 ++++++------
 t/t3407-rebase-abort.sh                 |  6 +++---
 t/t3420-rebase-autostash.sh             |  2 +-
 t/t3425-rebase-topology-merges.sh       |  8 ++++----
 t/t3432-rebase-fast-forward.sh          |  4 ++--
 t/t3433-rebase-options-compatibility.sh |  8 ++++----
 t/t5407-post-rewrite-hook.sh            | 12 ++++++------
 t/t7512-status-help.sh                  | 12 ++++++------
 11 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 71fd6396cd..0a491f2363 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 
 test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --am -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase -f --onto init HEAD^ &&
+		test_must_fail git rebase --am -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index a0b9438b22..50803958fd 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --am: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --am A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ae6e55ce79..743b7e511a 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 0c2c569f95..7ce617fc1f 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --am master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --am topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase reflog-onto &&
+	git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 910f218284..3e31826170 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --am --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --am master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 5f7e73cf83..3816159e20 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index fd8efe84fe..19700b025b 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --am
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 40388ccf9f..4b3cecce56 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index bd4d2d2f63..a07e1f276b 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '
 	new line 2
 	line 3
 	EOF
-	test_must_fail git rebase main side &&
+	test_must_fail git rebase --am main side &&
 	git rebase --abort &&
-	git rebase --ignore-whitespace main side &&
+	git rebase --am --ignore-whitespace main side &&
 	test_cmp expect file
 '
 
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '
 
 test_expect_success '--committer-date-is-author-date works with am backend' '
 	git commit --amend &&
-	git rebase --committer-date-is-author-date HEAD^ &&
+	git rebase --am --committer-date-is-author-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	git show HEAD --pretty="format:%ci" >committertime &&
 	test_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '
 # sets to +0530.
 test_expect_success '--ignore-date works with am backend' '
 	git commit --amend --date="$GIT_AUTHOR_DATE" &&
-	git rebase --ignore-date HEAD^ &&
+	git rebase --am --ignore-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	grep "+0000" authortime
 '
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 7344253bfb..a8a73616e4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --am' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --am --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --am --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --am --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 66d7a62797..d22b0acf2a 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --am in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --am in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --am conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 13/15] rebase tests: repeat some tests using the merge backend instead of am
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (11 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
                     ` (2 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In order to ensure the merge/interactive backend gets similar coverage
to the am one, add some tests for cases where previously only the am
backend was tested.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t5520-pull.sh                   | 17 +++++++++++++++--
 t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 602d996a33..3fff6a06fa 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -277,14 +277,27 @@ test_expect_success '--rebase' '
 	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=am pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index 860542aad0..d383ce8130 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '
 	)
 '
 
-test_expect_success 'rebase describes fake ancestor base' '
+test_expect_success 'rebase --merge describes parent of commit being picked' '
 	test_create_repo rebase &&
 	(
 		cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '
 		test_commit master file &&
 		git checkout -b side HEAD^ &&
 		test_commit side file &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --am describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
 		grep "||||||| constructed merge base" file
 	)
 '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 14/15] rebase: make the backend configurable via config setting
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (12 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-23 18:49   ` [PATCH v2 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt |  8 ++++++++
 builtin/rebase.c                | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d812..e6ae30c999 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,14 @@ rebase.useBuiltin::
 	is always used. Setting this will emit a warning, to alert any
 	remaining users that setting this now does nothing.
 
+rebase.backend::
+	Default backend to use for rebasing.  Possible choices are
+	'am' or 'merge' (note that the merge backend is sometimes also
+	refered to as the interactive backend or the interactive
+	machinery elsewhere in the docs).  Also, in the future, if the
+	merge backend gains all remaining capabilities of the am
+	backend, this setting may become unused.
+
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
 	rebase. False by default.
diff --git a/builtin/rebase.c b/builtin/rebase.c
index b7915fc0cb..d602b2da4c 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -60,6 +60,7 @@ enum empty_type {
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
+	const char *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -103,6 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
+		.default_backend = "am",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.backend")) {
+		return git_config_string(&opts->default_backend, var, value);
+	}
+
 	return git_default_config(var, value, data);
 }
 
@@ -1928,9 +1934,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
+		if (i >= 0) {
+			if (is_interactive(&options))
+				die(_("cannot combine am options with either "
+				      "interactive or merge options"));
+			else
+				options.type = REBASE_AM;
+		}
+	}
+
+	if (options.type == REBASE_UNSPECIFIED) {
+		if (!strcmp(options.default_backend, "merge"))
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "am"))
+			options.type = REBASE_AM;
+		else
+			die(_("Unknown rebase backend: %s"),
+			    options.default_backend);
 	}
 
 	switch (options.type) {
@@ -1943,10 +1963,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		options.state_dir = apply_dir();
 		break;
 	default:
-		/* the default rebase backend is `--am` */
-		options.type = REBASE_AM;
-		options.state_dir = apply_dir();
-		break;
+		BUG("options.type was just set above; should be unreachable.");
 	}
 
 	if (options.empty == EMPTY_UNSPECIFIED) {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v2 15/15] rebase: change the default backend from "am" to "merge"
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (13 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
@ 2019-12-23 18:49   ` Elijah Newren via GitGitGadget
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-23 18:49 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The am-backend drops information and thus limits what we can do:

  * lack of full tree information from the original commits means we
    cannot do directory rename detection and warn users that they might
    want to move some of their new files that they placed in old
    directories to prevent their becoming orphaned.[1]
  * reduction in context from only having a few lines beyond those
    changed means that when context lines are non-unique we can apply
    patches incorrectly.[2]
  * lack of access to original commits means that conflict marker
    annotation has less information available.

Also, the merge/interactive backend have far more abilities, appear to
currently have a slight performance advantage[3] and have room for more
optimizations than the am backend[4] (and work is underway to take
advantage of some of those possibilities).

[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt           |  2 +-
 builtin/rebase.c                       |  4 ++--
 t/t5520-pull.sh                        | 10 ++++++----
 t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index cf1ac2e359..e819889a31 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.
 --merge::
 	Use merging strategies to rebase.  When the recursive (default) merge
 	strategy is used, this allows rebase to be aware of renames on the
-	upstream side.
+	upstream side.  This is the default.
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
diff --git a/builtin/rebase.c b/builtin/rebase.c
index d602b2da4c..938a8840b1 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -104,7 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "am",	  	\
+		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1945,7 +1945,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			options.type = REBASE_MERGE;
+			imply_interactive(&options, "--merge");
 		else if (!strcmp(options.default_backend, "am"))
 			options.type = REBASE_AM;
 		else
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3fff6a06fa..4f9e7f7ff6 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
 	(
 		cd dst &&
 		test_must_fail git pull --rebase &&
-		find .git/rebase-apply -name "000*" >patches &&
-		test_line_count = 1 patches
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d..aec45bca3b 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases
  2019-12-23 18:49   ` [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
@ 2019-12-23 22:00     ` Denton Liu
  0 siblings, 0 replies; 161+ messages in thread
From: Denton Liu @ 2019-12-23 22:00 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, gitster, plroskin,
	alban.gruin, szeder.dev, Elijah Newren

Hi Elijah,

> Subject: contrib: change the prompt for interactive-based rebases

I'll also echo Gábor's comments and suggest that you use "git-prompt"
for the change area.

On Mon, Dec 23, 2019 at 06:49:55PM +0000, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> In the past, we had different prompts for different types of rebases:
>    REBASE: for am-based rebases
>    REBASE-m: for merge-based rebases
>    REBASE-i: for interactive-based rebases
> 
> It's not clear why this distinction was necessary or helpful; when the
> prompt was added in commit e75201963f67 ("Improve bash prompt to detect
> various states like an unfinished merge", 2007-09-30), it simply added
> these three different types.  Perhaps there was a useful purpose back
> then, but there have been some changes:
> 
>   * The merge backend was deleted after being implemented on top of the
>     interactive backend, causing the prompt for merge-based rebases to
>     change from REBASE-m to REBASE-i.
>   * The interactive backend is used for multiple different types of
>     non-interactive rebases, so the "-i" part of the prompt doesn't
>     really mean what it used to.
>   * Rebase backends have gained more abilities and have a great deal of
>     overlap, sometimes making it hard to distinguish them.
>   * Behavioral differences between the backends have also been ironed
>     out.
>   * We want to change the default backend from am to interactive, which
>     means people would get "REBASE-i" by default if we didn't change
>     the prompt, and only if they specified --am or --whitespace or -C
>     would they get the "REBASE" prompt.
>   * In the future, we plan to have "--whitespace", "-C", and even "--am"
>     run the interactive backend once it can handle everything the
>     am-backend can.
> 
> For all these reasons, make the prompt for any type of rebase just be
> "REBASE".
> 
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>  contrib/completion/git-prompt.sh | 4 ++--
>  t/t9903-bash-prompt.sh           | 8 ++++----
>  2 files changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
> index 1d510cd47b..8f8a22ba60 100644
> --- a/contrib/completion/git-prompt.sh
> +++ b/contrib/completion/git-prompt.sh
> @@ -430,9 +430,9 @@ __git_ps1 ()
>  		__git_eread "$g/rebase-merge/msgnum" step
>  		__git_eread "$g/rebase-merge/end" total
>  		if [ -f "$g/rebase-merge/interactive" ]; then
> -			r="|REBASE-i"
> +			r="|REBASE"
>  		else
> -			r="|REBASE-m"
> +			r="|REBASE"

We should just drop the if here since both arms are the same..

Thanks,

Denton

>  		fi
>  	else
>  		if [ -d "$g/rebase-apply" ]; then
> diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
> index 88bc733ad6..7ca35d358d 100755
> --- a/t/t9903-bash-prompt.sh
> +++ b/t/t9903-bash-prompt.sh
> @@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
>  '
>  
>  test_expect_success 'prompt - interactive rebase' '
> -	printf " (b1|REBASE-i 2/3)" >expected &&
> +	printf " (b1|REBASE 2/3)" >expected &&
>  	write_script fake_editor.sh <<-\EOF &&
>  		echo "exec echo" >"$1"
>  		echo "edit $(git log -1 --format="%h")" >>"$1"
> @@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
>  '
>  
>  test_expect_success 'prompt - rebase merge' '
> -	printf " (b2|REBASE-i 1/3)" >expected &&
> +	printf " (b2|REBASE 1/3)" >expected &&
>  	git checkout b2 &&
>  	test_when_finished "git checkout master" &&
>  	test_must_fail git rebase --merge b1 b2 &&
> @@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
>  	test_cmp expected "$actual"
>  '
>  
> -test_expect_success 'prompt - rebase' '
> +test_expect_success 'prompt - rebase am' '
>  	printf " (b2|REBASE 1/3)" >expected &&
>  	git checkout b2 &&
>  	test_when_finished "git checkout master" &&
> -	test_must_fail git rebase b1 b2 &&
> +	test_must_fail git rebase --am b1 b2 &&
>  	test_when_finished "git rebase --abort" &&
>  	__git_ps1 >"$actual" &&
>  	test_cmp expected "$actual"
> -- 
> gitgitgadget
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v3 00/15] rebase: make the default backend configurable
  2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
                     ` (14 preceding siblings ...)
  2019-12-23 18:49   ` [PATCH v2 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2019-12-24 19:54   ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
                       ` (15 more replies)
  15 siblings, 16 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano

This series does a lot of work around making the default rebase backend
configurable, and switching the default from the am backend to the
merge/interactive one.

See the cover letter for v1[*] for a more detailed motivation for the
series, the type of changes being made, and areas I'd like to reviewers to
focus on.

Changes since v2:

 * Tweaked patch 11 (commit message, and condensing if-else block that was
   same on both sides), as suggested by Denton

[*] 
https://lore.kernel.org/git/pull.679.git.git.1576861788.gitgitgadget@gmail.com/

Elijah Newren (15):
  rebase: extend the options for handling of empty commits
  t3406: simplify an already simple test
  rebase, sequencer: remove the broken GIT_QUIET handling
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase: fix handling of restrict_revision
  t3432: make these tests work with either am or merge backends
  rebase: allow more types of rebases to fast-forward
  git-rebase.txt: add more details about behavioral differences of
    backends
  rebase: move incompatibility checks between backend options a bit
    earlier
  rebase: add an --am option
  git-prompt: change the prompt for interactive-based rebases
  rebase tests: mark tests specific to the am-backend with --am
  rebase tests: repeat some tests using the merge backend instead of am
  rebase: make the backend configurable via config setting
  rebase: change the default backend from "am" to "merge"

 Documentation/config/rebase.txt         |   8 ++
 Documentation/git-rebase.txt            | 150 ++++++++++++++++----
 builtin/rebase.c                        | 177 +++++++++++++++++++-----
 contrib/completion/git-prompt.sh        |   6 +-
 rebase-interactive.c                    |   4 +-
 rebase-interactive.h                    |   2 +-
 sequencer.c                             |  80 ++++++++---
 sequencer.h                             |   6 +-
 t/t3400-rebase.sh                       |  36 ++++-
 t/t3401-rebase-and-am-rename.sh         |   4 +-
 t/t3404-rebase-interactive.sh           |   2 +-
 t/t3406-rebase-message.sh               |  19 ++-
 t/t3407-rebase-abort.sh                 |   6 +-
 t/t3420-rebase-autostash.sh             |   2 +-
 t/t3421-rebase-topology-linear.sh       |   4 +-
 t/t3424-rebase-empty.sh                 |  89 ++++++++++++
 t/t3425-rebase-topology-merges.sh       |   8 +-
 t/t3427-rebase-subtree.sh               |  16 ++-
 t/t3432-rebase-fast-forward.sh          |  54 ++++----
 t/t3433-rebase-options-compatibility.sh |  13 +-
 t/t5407-post-rewrite-hook.sh            |  12 +-
 t/t5520-pull.sh                         |  27 +++-
 t/t6047-diff3-conflict-markers.sh       |  13 +-
 t/t7512-status-help.sh                  |  12 +-
 t/t9106-git-svn-commit-diff-clobber.sh  |   3 +-
 t/t9903-bash-prompt.sh                  |   8 +-
 26 files changed, 576 insertions(+), 185 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh


base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v3
Pull-Request: https://github.com/git/git/pull/679

Range-diff vs v2:

  1:  1c2b77e94d =  1:  1c2b77e94d rebase: extend the options for handling of empty commits
  2:  bd3c5ec155 =  2:  bd3c5ec155 t3406: simplify an already simple test
  3:  49388b79fd =  3:  49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling
  4:  478479358f =  4:  478479358f rebase: make sure to pass along the quiet flag to the sequencer
  5:  ee26f5a161 =  5:  ee26f5a161 rebase: fix handling of restrict_revision
  6:  34a69def33 =  6:  34a69def33 t3432: make these tests work with either am or merge backends
  7:  f2c92853b4 =  7:  f2c92853b4 rebase: allow more types of rebases to fast-forward
  8:  b307340f7c =  8:  b307340f7c git-rebase.txt: add more details about behavioral differences of backends
  9:  7c3f2e07f3 =  9:  7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier
 10:  1df11f0b51 = 10:  1df11f0b51 rebase: add an --am option
 11:  94b5a3051d ! 11:  ff43593211 contrib: change the prompt for interactive-based rebases
     @@ -1,6 +1,6 @@
      Author: Elijah Newren <newren@gmail.com>
      
     -    contrib: change the prompt for interactive-based rebases
     +    git-prompt: change the prompt for interactive-based rebases
      
          In the past, we had different prompts for different types of rebases:
             REBASE: for am-based rebases
     @@ -40,17 +40,18 @@
       --- a/contrib/completion/git-prompt.sh
       +++ b/contrib/completion/git-prompt.sh
      @@
     + 		__git_eread "$g/rebase-merge/head-name" b
       		__git_eread "$g/rebase-merge/msgnum" step
       		__git_eread "$g/rebase-merge/end" total
     - 		if [ -f "$g/rebase-merge/interactive" ]; then
     +-		if [ -f "$g/rebase-merge/interactive" ]; then
      -			r="|REBASE-i"
     -+			r="|REBASE"
     - 		else
     +-		else
      -			r="|REBASE-m"
     -+			r="|REBASE"
     - 		fi
     +-		fi
     ++		r="|REBASE"
       	else
       		if [ -d "$g/rebase-apply" ]; then
     + 			__git_eread "$g/rebase-apply/next" step
      
       diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
       --- a/t/t9903-bash-prompt.sh
 12:  c905d288bf = 12:  99388f24e5 rebase tests: mark tests specific to the am-backend with --am
 13:  0287881361 = 13:  c2ba6317bf rebase tests: repeat some tests using the merge backend instead of am
 14:  ec782e711c = 14:  8bec6df51a rebase: make the backend configurable via config setting
 15:  7adcbc0bc5 = 15:  044853fd61 rebase: change the default backend from "am" to "merge"

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v3 01/15] rebase: extend the options for handling of empty commits
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2020-01-07 14:37       ` Phillip Wood
  2019-12-24 19:54     ` [PATCH v3 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
                       ` (14 subsequent siblings)
  15 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Extend the interactive machinery with the ability to handle the full
spread of options for how to handle commits that either start or become
empty (by "become empty" I mean the changes in a commit are a subset of
changes that exist upstream, so the net effect of applying the commit is
no changes).  Introduce a new command line flag for selecting the
desired behavior:
    --empty={drop,keep,ask}
with the definitions:
    drop: drop empty commits
    keep: keep empty commits
    ask:  provide the user a chance to interact and pick what to do with
          empty commits on a case-by-case basis

Note that traditionally, am-based rebases have always dropped commits
that either started or became empty, while interactive-based rebases
have defaulted to ask (and provided an option to keep commits that
started empty).  This difference made sense since users of an am-based
rebase just wanted to quickly batch apply a sequence of commits, while
users editing a todo list will likely want the chance to interact and
handle unusual cases on a case-by-case basis.  However, not all rebases
using the interactive machinery are explicitly interactive anymore.  In
particular --merge was always meant to behave more like --am: just
rebase a batch of commits without popping up a todo list.

If the --empty flag is not specified, pick defaults as follows:
    explicitly interactive: ask
    --exec: keep (exec is about checking existing commits, and often
                  used without actually changing the base.  Thus the
                  expectation is that the user doesn't necessarily want
                  anything to change; they just want to test).
    otherwise: drop

Also, this commit makes --keep-empty just imply --empty=keep, and hides
it from help so that we aren't confusing users with different ways to do
the same thing.  (I could have added a --drop-empty flag, but then that
invites users to specify both --keep-empty and --drop-empty and we have
to add sanity checking around that; it seems cleaner to have a single
multi-valued option.)  This actually fixes --keep-empty too; previously,
it only meant to sometimes keep empty commits, in particular commits
which started empty would be kept.  But it would still error out and ask
the user what to do with commits that became empty.  Now it keeps empty
commits, as instructed.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt      | 35 ++++++------
 builtin/rebase.c                  | 83 +++++++++++++++++++++++++---
 rebase-interactive.c              |  4 +-
 rebase-interactive.h              |  2 +-
 sequencer.c                       | 74 +++++++++++++++++++------
 sequencer.h                       |  6 ++-
 t/t3421-rebase-topology-linear.sh |  4 +-
 t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++
 t/t3427-rebase-subtree.sh         | 16 +++---
 9 files changed, 263 insertions(+), 50 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d0e2d27cc..ff32ca1080 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--empty={drop,keep,ask}::
+	How to handle commits that become empty (because they contain a
+	subset of already upstream changes) or start empty.  With drop
+	(the default), commits that start or become empty are dropped.
+	With keep (implied by --exec), such commits are kept.  With ask
+	(implied by --interactive), the rebase will halt when an empty
+	commit is applied allowing you to choose whether to drop it or
+	commit it.  Also with ask, if the rebase is interactive then
+	commits which start empty will be commented out in the todo
+	action list (giving you a chance to uncomment).
++
+Note that this has no effect on commits which are already upstream (as
+can be checked via `git log --cherry-mark ...`), which are always
+dropped by rebase.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --keep-empty::
-	Keep the commits that do not change anything from its
-	parents in the result.
+	Deprecated alias for what is now known as --empty=keep.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -569,6 +585,7 @@ are incompatible with the following options:
  * --interactive
  * --exec
  * --keep-empty
+ * --empty=
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --ignore-whitespace
  * --preserve-merges and --committer-date-is-author-date
  * --preserve-merges and --ignore-date
+ * --preserve-merges and --empty=
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES
 
 There are some subtle differences how the backends behave.
 
-Empty commits
-~~~~~~~~~~~~~
-
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
-
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
-
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ddf33bc9d4..6903249307 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -50,8 +50,16 @@ enum rebase_type {
 	REBASE_PRESERVE_MERGES
 };
 
+enum empty_type {
+	EMPTY_UNSPECIFIED = -1,
+	EMPTY_DROP,
+	EMPTY_KEEP,
+	EMPTY_ASK
+};
+
 struct rebase_options {
 	enum rebase_type type;
+	enum empty_type empty;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -77,7 +85,6 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
-	int keep_empty;
 	int autosquash;
 	int ignore_whitespace;
 	char *gpg_sign_opt;
@@ -95,6 +102,7 @@ struct rebase_options {
 
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
+		.empty = EMPTY_UNSPECIFIED,	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
 	replay.allow_empty = 1;
 	replay.allow_empty_message = opts->allow_empty_message;
+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
+					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,
 
 	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
-	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
+	flags |= (opts->empty == EMPTY_ASK &&
+		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
+			TODO_LIST_ASK_EMPTY : 0;
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,
 	return ret;
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+				int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->empty = EMPTY_KEEP;
+	opts->type = REBASE_INTERACTIVE;
+	return 0;
+}
+
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
-		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
 			 N_("allow commits with empty messages")),
 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
@@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		opts->allow_rerere_autoupdate ?
 			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
 			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
+	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
 	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static enum empty_type parse_empty_value(const char *value)
+{
+	if (!strcasecmp(value, "drop"))
+		return EMPTY_DROP;
+	else if (!strcasecmp(value, "keep"))
+		return EMPTY_KEEP;
+	else if (!strcasecmp(value, "ask"))
+		return EMPTY_ASK;
+
+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+	enum empty_type value = parse_empty_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+
+	options->empty = value;
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_BOOL('k', "keep-empty", &options.keep_empty,
-			 N_("preserve empty commits during rebase")),
+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
+			       N_("how to handle empty commits"),
+			       PARSE_OPT_NONEG, parse_opt_empty),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
-	if (options.keep_empty)
-		imply_interactive(&options, "--keep-empty");
+	if (options.empty != EMPTY_UNSPECIFIED)
+		imply_interactive(&options, "--empty");
 
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
@@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
+	if (options.empty == EMPTY_UNSPECIFIED) {
+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+			options.empty = EMPTY_ASK;
+		else if (exec.nr > 0)
+			options.empty = EMPTY_KEEP;
+		else
+			options.empty = EMPTY_DROP;
+	}
 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
diff --git a/rebase-interactive.c b/rebase-interactive.c
index aa18ae82b7..ad82bf77df 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
 	return MISSING_COMMIT_CHECK_IGNORE;
 }
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf)
 {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,
 
 	strbuf_add_commented_lines(buf, msg, strlen(msg));
 
-	if (!keep_empty) {
+	if (!no_ask_empty) {
 		msg = _("Note that empty commits are commented out");
 		strbuf_add_commented_lines(buf, msg, strlen(msg));
 	}
diff --git a/rebase-interactive.h b/rebase-interactive.h
index 44dbb06311..f531e00ba7 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -5,7 +5,7 @@ struct strbuf;
 struct repository;
 struct todo_list;
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(unsigned no_ask_empty, int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf);
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/sequencer.c b/sequencer.c
index 763ccbbc45..d2c11f34b7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
 static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
 
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,
 	empty_commit = is_original_commit_empty(commit);
 	if (empty_commit < 0)
 		return empty_commit;
-	if (!empty_commit)
+	if (!empty_commit || opts->ask_on_initially_empty)
 		return 0;
 	else
 		return 1;
@@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,
 	char *author = NULL;
 	struct commit_message msg = { NULL, NULL, NULL, NULL };
 	struct strbuf msgbuf = STRBUF_INIT;
-	int res, unborn = 0, reword = 0, allow;
+	int res, unborn = 0, reword = 0, allow, drop_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,
 		goto leave;
 	}
 
-	allow = allow_empty(r, opts, commit);
-	if (allow < 0) {
-		res = allow;
-		goto leave;
-	} else if (allow)
-		flags |= ALLOW_EMPTY;
-	if (!opts->no_commit) {
+	drop_commit = 0;
+	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
+		drop_commit = 1;
+		fprintf(stderr, _("No changes -- Patch already applied."));
+	} else {
+		allow = allow_empty(r, opts, commit);
+		if (allow < 0) {
+			res = allow;
+			goto leave;
+		} else if (allow) {
+			flags |= ALLOW_EMPTY;
+		}
+	}
+	if (!opts->no_commit && !drop_commit) {
 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 			res = do_commit(r, msg_file, author, opts, flags);
 		else
@@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
 	else if (!strcmp(key, "options.allow-empty-message"))
 		opts->allow_empty_message =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.drop-redundant-commits"))
+		opts->drop_redundant_commits =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.keep-redundant-commits"))
 		opts->keep_redundant_commits =
 			git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "options.ask_on_initially_empty"))
+		opts->ask_on_initially_empty =
+			git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.signoff"))
 		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
 	else if (!strcmp(key, "options.record-origin"))
@@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
+		if (file_exists(rebase_path_drop_redundant_commits()))
+			opts->drop_redundant_commits = 1;
+
+		if (file_exists(rebase_path_keep_redundant_commits()))
+			opts->keep_redundant_commits = 1;
+
+		if (file_exists(rebase_path_ask_on_initially_empty()))
+			opts->ask_on_initially_empty = 1;
+
 		read_strategy_opts(opts, &buf);
 		strbuf_release(&buf);
 
@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_cdate_is_adate(), "%s", "");
 	if (opts->ignore_date)
 		write_file(rebase_path_ignore_date(), "%s", "");
+	if (opts->drop_redundant_commits)
+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
+	if (opts->keep_redundant_commits)
+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
+	if (opts->ask_on_initially_empty)
+		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
 	if (opts->allow_empty_message)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.allow-empty-message", "true");
+	if (opts->drop_redundant_commits)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.drop-redundant-commits", "true");
 	if (opts->keep_redundant_commits)
 		res |= git_config_set_in_file_gently(opts_file,
 				"options.keep-redundant-commits", "true");
+	if (opts->ask_on_initially_empty)
+		res |= git_config_set_in_file_gently(opts_file,
+				"options.ask_on_initially_empty", "true");
 	if (opts->signoff)
 		res |= git_config_set_in_file_gently(opts_file,
 					"options.signoff", "true");
@@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 				   struct rev_info *revs, struct strbuf *out,
 				   unsigned flags)
 {
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
 	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		is_empty = is_original_commit_empty(commit);
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
+		if (is_empty && drop_empty)
+			continue;
 
 		strbuf_reset(&oneline);
 		pretty_print_commit(pp, commit, &oneline);
@@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		if (!to_merge) {
 			/* non-merge commit: easy case */
 			strbuf_reset(&buf);
-			if (!keep_empty && is_empty)
+			if (is_empty && ask_empty)
 				strbuf_addf(&buf, "%c ", comment_line_char);
 			strbuf_addf(&buf, "%s %s %s", cmd_pick,
 				    oid_to_hex(&commit->object.oid),
@@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	struct pretty_print_context pp = {0};
 	struct rev_info revs;
 	struct commit *commit;
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
 	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
@@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 		return make_script_with_merges(&pp, &revs, out, flags);
 
 	while ((commit = get_revision(&revs))) {
-		int is_empty  = is_original_commit_empty(commit);
+		int is_empty = is_original_commit_empty(commit);
 
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
-		if (!keep_empty && is_empty)
+		if (is_empty && drop_empty)
+			continue;
+		if (is_empty && ask_empty)
 			strbuf_addf(out, "%c ", comment_line_char);
 		strbuf_addf(out, "%s %s ", insn,
 			    oid_to_hex(&commit->object.oid));
@@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
 
 	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
 	if (flags & TODO_LIST_APPEND_TODO_HELP)
-		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
+				 count_commands(todo_list),
 				 shortrevisions, shortonto, &buf);
 
 	res = write_message(buf.buf, buf.len, file, 0);
diff --git a/sequencer.h b/sequencer.h
index e9a0e03ea2..1c3abb661c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -39,7 +39,9 @@ struct replay_opts {
 	int allow_rerere_auto;
 	int allow_empty;
 	int allow_empty_message;
+	int drop_redundant_commits;
 	int keep_redundant_commits;
+	int ask_on_initially_empty;
 	int verbose;
 	int quiet;
 	int reschedule_failed_exec;
@@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-#define TODO_LIST_KEEP_EMPTY (1U << 0)
+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);
  * `--onto`, we do not want to re-generate the root commits.
  */
 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
+#define TODO_LIST_DROP_EMPTY (1U << 7)
+#define TODO_LIST_ASK_EMPTY (1U << 8)
 
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 325072b0a3..d23e0bf778 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -230,7 +230,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
new file mode 100755
index 0000000000..9d52e1417f
--- /dev/null
+++ b/t/t3424-rebase-empty.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_success 'rebase --merge --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	test_must_fail git rebase --skip &&
+	git commit --allow-empty &&
+	git rebase --continue &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
+
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+
+test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index bec48e6a1f..468ebc1bef 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	test_must_fail git rebase --skip &&
+	: last pick was an empty commit that has no changes, but we want to keep it &&
+	git commit --allow-empty &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 02/15] t3406: simplify an already simple test
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
                       ` (13 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

When the merge backend was re-implemented on top of the interactive
backend, the output of rebase --merge changed a little.  This change
allowed this test to be simplified, though it wasn't noticed until now.
Simplify the testcase a little.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3406-rebase-message.sh | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index b393e1e9fe..0c2c569f95 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -18,11 +18,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 03/15] rebase, sequencer: remove the broken GIT_QUIET handling
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
                       ` (12 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The GIT_QUIET environment variable was used to signal the non-am
backends that the rebase should perform quietly.  The preserve-merges
backend does not make use of the quiet flag anywhere (other than to
write out its state whenever it writes state), and this mechanism was
broken in the conversion from shell to C.  Since this environment
variable was specifically designed for scripts and the only backend that
would still use it is no longer a script, just gut this code.

A subsequent commit will fix --quiet for the interactive/merge backend
in a different way.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 6 ++----
 sequencer.c      | 6 ++----
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6903249307..32026a62e8 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -718,8 +718,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 	write_file(state_dir_path("orig-head", opts), "%s",
 		   oid_to_hex(&opts->orig_head));
-	write_file(state_dir_path("quiet", opts), "%s",
-		   opts->flags & REBASE_NO_QUIET ? "" : "t");
+	if (!(opts->flags & REBASE_NO_QUIET))
+		write_file(state_dir_path("quiet", opts), "%s", "");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -1178,8 +1178,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
 	add_var(&script_snippet, "git_am_opt", buf.buf);
 	strbuf_release(&buf);
diff --git a/sequencer.c b/sequencer.c
index d2c11f34b7..71062212a5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2688,8 +2688,6 @@ static void write_strategy_opts(struct replay_opts *opts)
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const char *orig_head)
 {
-	const char *quiet = getenv("GIT_QUIET");
-
 	if (head_name)
 		write_file(rebase_path_head_name(), "%s\n", head_name);
 	if (onto)
@@ -2698,8 +2696,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 	if (orig_head)
 		write_file(rebase_path_orig_head(), "%s\n", orig_head);
 
-	if (quiet)
-		write_file(rebase_path_quiet(), "%s\n", quiet);
+	if (opts->quiet)
+		write_file(rebase_path_quiet(), "%s", "");
 	if (opts->verbose)
 		write_file(rebase_path_verbose(), "%s", "");
 	if (opts->strategy)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 04/15] rebase: make sure to pass along the quiet flag to the sequencer
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (2 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
                       ` (11 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  | 3 ++-
 t/t3400-rebase.sh | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 32026a62e8..5014c9a437 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
 	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
 					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
+	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
@@ -1502,7 +1503,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			 N_("allow pre-rebase hook to run")),
 		OPT_NEGBIT('q', "quiet", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+			   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
 		OPT_BIT('v', "verbose", &options.flags,
 			N_("display a diffstat of what changed upstream"),
 			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 221b35f2df..79762b989a 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
 	git rebase -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	(
 		echo "One" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 05/15] rebase: fix handling of restrict_revision
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (3 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
                       ` (10 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

restrict_revision in the original shell script was an excluded revision
range.  It is also treated that way by the am-backend.  In the
conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the
interactive-backend accidentally treated it as a positive revision
rather than a negated one.

This was missed as there were no tests in the testsuite that tested an
interactive rebase with fork-point behavior.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  |  4 ++--
 t/t3400-rebase.sh | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 5014c9a437..f1de5c8186 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -364,8 +364,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 
 	argv_array_pushl(&make_script_args, "", revisions, NULL);
 	if (opts->restrict_revision)
-		argv_array_push(&make_script_args,
-				oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_pushf(&make_script_args, "^%s",
+				 oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
 				    make_script_args.argc, make_script_args.argv,
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 79762b989a..71fd6396cd 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
 	git rebase master
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
 	git rebase &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 06/15] t3432: make these tests work with either am or merge backends
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (4 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
                       ` (9 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3432 had several stress tests for can_fast_forward(), whose intent was
to ensure we were using the optimization of just fast forwarding when
possible.  However, these tests verified that fast forwards had happened
based on the output that rebase printed to the terminal.  We can instead
test more directly that we actually fast-forwarded by checking the
reflog, which also has the side effect of making the tests applicable
for the merge/interactive backend.

This change does lose the distinction between "noop" and "noop-force",
but as stated in commit c9efc216830f ("t3432: test for --no-ff's
interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo,
just test for what we're currently doing.".

This change does not actually run these tests with the merge/interactive
backend; instead this is just a preparatory commit.  A subsequent commit
which fixes can_fast_forward() to work with that backend will then also
change t3432 to add tests of that backend as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 92f95b57da..7432c0e241 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -44,19 +44,15 @@ test_rebase_same_head_ () {
 	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
 		oldhead=\$(git rev-parse HEAD) &&
 		test_when_finished 'git reset --hard \$oldhead' &&
+		cp .git/logs/HEAD expect &&
 		git rebase$flag $* >stdout &&
 		if test $what = work
 		then
-			# Must check this case first, for 'is up to
-			# date, rebase forced[...]rewinding head' cases
-			test_i18ngrep 'rewinding head' stdout
+			old=\$(wc -l <expect) &&
+			test_line_count '-gt' \$old .git/logs/HEAD
 		elif test $what = noop
 		then
-			test_i18ngrep 'is up to date' stdout &&
-			test_i18ngrep ! 'rebase forced' stdout
-		elif test $what = noop-force
-		then
-			test_i18ngrep 'is up to date, rebase forced' stdout
+			test_cmp expect .git/logs/HEAD
 		fi &&
 		newhead=\$(git rev-parse HEAD) &&
 		if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () {
 
 changes='no changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '
 
 changes='our changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '
 '
 
 changes='our and their changes'
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
 test_rebase_same_head success noop same success work diff --onto master... master
 test_rebase_same_head success noop same success work diff --keep-base master
 test_rebase_same_head success noop same success work diff --keep-base
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 07/15] rebase: allow more types of rebases to fast-forward
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (5 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
                       ` (8 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we dis-allowed rebases using the interactive backend from
performing a fast-forward to short-circuit the rebase operation.  This
made sense for explicitly interactive rebases and some implicitly
interactive rebases, but certainly became overly stringent when the
merge backend was re-implemented via the interactive backend.

Just as the am-based rebase has always had to disable the fast-forward
based on a variety of conditions or flags (e.g. --signoff, --whitespace,
etc.), we need to do the same but now with a few more options.  However,
continuing to use REBASE_FORCE for tracking this is problematic because
the interactive backend used it for a different purpose.  (When
REBASE_FORCE wasn't set, the interactive backend would not fast-forward
the whole series but would fast-forward individual "pick" commits at the
beginning of the todo list, and then a squash or something would cause
it to start generating new commits.)  So, introduce a new
allow_preemptive_ff flag contained within cmd_rebase() and use it to
track whether we are going to allow a pre-emptive fast-forward that
short-circuits the whole rebase.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c               | 18 ++++++++++++++----
 t/t3432-rebase-fast-forward.sh |  2 ++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index f1de5c8186..7027e34567 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1493,6 +1493,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
+	int allow_preemptive_ff = 1;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	    options.ignore_date)
 		options.flags |= REBASE_FORCE;
 
+	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+	    (action != ACTION_NONE) ||
+	    (exec.nr > 0) ||
+	    options.autosquash) {
+		allow_preemptive_ff = 0;
+	}
+
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
 		if (!strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
-			options.flags |= REBASE_FORCE;
+			allow_preemptive_ff = 0;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -2144,12 +2152,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	/*
 	 * Check if we are already based on onto with linear history,
 	 * in which case we could fast-forward without replacing the commits
-	 * with new commits recreated by replaying their changes. This
-	 * optimization must not be done if this is an interactive rebase.
+	 * with new commits recreated by replaying their changes.
+	 *
+	 * Note that can_fast_forward() initializes merge_base, so we have to
+	 * call it before checking allow_preemptive_ff.
 	 */
 	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
 		    &options.orig_head, &merge_base) &&
-	    !is_interactive(&options)) {
+	    allow_preemptive_ff) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 7432c0e241..40388ccf9f 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -30,6 +30,8 @@ test_rebase_same_head () {
 	shift &&
 	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 08/15] git-rebase.txt: add more details about behavioral differences of backends
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (6 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
                       ` (7 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt            | 102 +++++++++++++++++++++---
 t/t3433-rebase-options-compatibility.sh |   5 +-
 2 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index ff32ca1080..f1ace07c38 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used
 with `--keep-base` in order to drop those commits from your branch.
 
 --ignore-whitespace::
-	Behaves differently depending on which backend is selected.
-+
-'am' backend: When applying a patch, ignore changes in whitespace in
-context lines if necessary.
-+
-'interactive' backend: Treat lines with only whitespace changes as
-unchanged for the sake of a three-way merge.
+	Ignore whitespace-only changes in the commits being rebased,
+	which may avoid "unnecessary" conflicts.  (Both backends
+	currently have differing edgecase bugs with this option; see
+	BEHAVIORAL DIFFERENCES.)
 
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave.
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Directory rename heuristics are enabled in the merge and interactive
-backends.  Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the am backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge and interactive
+backends to provide you warnings in such cases.
+
+Context
+~~~~~~~
+
+The am backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally).  Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes.  The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file.  The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines.  However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked.  There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The interactive backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from.  Since the am backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary.  Also, when merge.conflictStyle is
+set to diff3, the am backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The interactive backend works with the full commits on both sides of
+history and thus has no such limitations.
+
+--ignore-whitespace
+~~~~~~~~~~~~~~~~~~~
+
+The --ignore-whitespace option is supposed to ignore whitespace-only
+changes if it allows the code to merge cleanly.  Unfortunately, the
+different backends implement this differently, and both have different
+edge case bugs.
++
+'am' backend: When applying a patch, ignore changes in whitespace in
+context lines if necessary.  (Which implies that if the whitespace
+change was not in the context lines but on a line with a real change,
+then the rebase will still fail with "unnecessary" content conflicts.)
++
+'interactive' backend: Treat lines with only whitespace changes as
+unchanged for the sake of a three-way merge.  This means that if one
+side made no changes and the commits being rebased had whitespace-only
+changes, those whitespaces fixups will be discarded despite the fact
+that they present no content conflict.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+  the changes made in the reflog, though both will make use of the
+  word "rebase".
+
+* Progress, informational, and error messages: The two backends
+  provide slightly different progress and informational messages.
+  Also, the am backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the interactive backend
+  writes them to stderr.
+
+* State directories: The two backends keep their state in different
+  directories under .git/
 
 include::merge-strategies.txt[]
 
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index 5166f158dd..bd4d2d2f63 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe
 GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
 export GIT_AUTHOR_DATE
 
-# This is a special case in which both am and interactive backends
-# provide the same output. It was done intentionally because
-# both the backends fall short of optimal behaviour.
+# This is a common case in which both am and interactive backends
+# provide the same output with --ignore-whitespace.
 test_expect_success 'setup' '
 	git checkout -b topic &&
 	q_to_tab >file <<-\EOF &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 09/15] rebase: move incompatibility checks between backend options a bit earlier
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (7 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
                       ` (6 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 7027e34567..d2b99e9908 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1906,6 +1906,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
+	if (options.git_am_opts.argc) {
+		/* all am options except -q are compatible only with --am */
+		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+			if (strcmp(options.git_am_opts.argv[i], "-q"))
+				break;
+
+		if (is_interactive(&options) && i >= 0)
+			die(_("cannot combine am options with either "
+			      "interactive or merge options"));
+	}
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
@@ -1936,17 +1947,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (reschedule_failed_exec >= 0)
 		options.reschedule_failed_exec = reschedule_failed_exec;
 
-	if (options.git_am_opts.argc) {
-		/* all am options except -q are compatible only with --am */
-		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
-			if (strcmp(options.git_am_opts.argv[i], "-q"))
-				break;
-
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
-	}
-
 	if (options.signoff) {
 		if (options.type == REBASE_PRESERVE_MERGES)
 			die("cannot combine '--signoff' with "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 10/15] rebase: add an --am option
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (8 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2020-01-07 14:43       ` Phillip Wood
  2019-12-24 19:54     ` [PATCH v3 11/15] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
                       ` (5 subsequent siblings)
  15 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Currently, this option doesn't do anything except error out if any
options requiring the interactive-backend are also passed.  However,
when we make the default backend configurable later in this series, this
flag will provide a way to override the config setting.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 11 ++++++++++-
 builtin/rebase.c             | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index f1ace07c38..cf1ac2e359 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--am:
+	Use git-am internally to rebase.  This option may become a
+	no-op in the future once the interactive backend handles
+	everything the am one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --empty={drop,keep,ask}::
 	How to handle commits that become empty (because they contain a
 	subset of already upstream changes) or start empty.  With drop
@@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.
+	ever ignored.  Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	This flag is passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
+	Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
+ * --am
  * --whitespace
  * -C
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index d2b99e9908..b7915fc0cb 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	return res && is_linear_history(onto, head);
 }
 
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_AM;
+
+	return 0;
+}
+
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
+		{ OPTION_CALLBACK, 0, "am", &options, NULL,
+			N_("use apply-mail strategies to rebase"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
 			N_("use merging strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc) {
+	if (options.git_am_opts.argc || options.type == REBASE_AM) {
 		/* all am options except -q are compatible only with --am */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 11/15] git-prompt: change the prompt for interactive-based rebases
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (9 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
                       ` (4 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we had different prompts for different types of rebases:
   REBASE: for am-based rebases
   REBASE-m: for merge-based rebases
   REBASE-i: for interactive-based rebases

It's not clear why this distinction was necessary or helpful; when the
prompt was added in commit e75201963f67 ("Improve bash prompt to detect
various states like an unfinished merge", 2007-09-30), it simply added
these three different types.  Perhaps there was a useful purpose back
then, but there have been some changes:

  * The merge backend was deleted after being implemented on top of the
    interactive backend, causing the prompt for merge-based rebases to
    change from REBASE-m to REBASE-i.
  * The interactive backend is used for multiple different types of
    non-interactive rebases, so the "-i" part of the prompt doesn't
    really mean what it used to.
  * Rebase backends have gained more abilities and have a great deal of
    overlap, sometimes making it hard to distinguish them.
  * Behavioral differences between the backends have also been ironed
    out.
  * We want to change the default backend from am to interactive, which
    means people would get "REBASE-i" by default if we didn't change
    the prompt, and only if they specified --am or --whitespace or -C
    would they get the "REBASE" prompt.
  * In the future, we plan to have "--whitespace", "-C", and even "--am"
    run the interactive backend once it can handle everything the
    am-backend can.

For all these reasons, make the prompt for any type of rebase just be
"REBASE".

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 contrib/completion/git-prompt.sh | 6 +-----
 t/t9903-bash-prompt.sh           | 8 ++++----
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd47b..014cd7c3cf 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -429,11 +429,7 @@ __git_ps1 ()
 		__git_eread "$g/rebase-merge/head-name" b
 		__git_eread "$g/rebase-merge/msgnum" step
 		__git_eread "$g/rebase-merge/end" total
-		if [ -f "$g/rebase-merge/interactive" ]; then
-			r="|REBASE-i"
-		else
-			r="|REBASE-m"
-		fi
+		r="|REBASE"
 	else
 		if [ -d "$g/rebase-apply" ]; then
 			__git_eread "$g/rebase-apply/next" step
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 88bc733ad6..7ca35d358d 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
 '
 
 test_expect_success 'prompt - interactive rebase' '
-	printf " (b1|REBASE-i 2/3)" >expected &&
+	printf " (b1|REBASE 2/3)" >expected &&
 	write_script fake_editor.sh <<-\EOF &&
 		echo "exec echo" >"$1"
 		echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
 '
 
 test_expect_success 'prompt - rebase merge' '
-	printf " (b2|REBASE-i 1/3)" >expected &&
+	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
 	test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
+test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --am b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 12/15] rebase tests: mark tests specific to the am-backend with --am
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (10 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 11/15] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
                       ` (3 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

We have many rebase tests in the testsuite, and often the same test is
repeated multiple times just testing different backends.  For those
tests that were specifically trying to test the am backend, add the --am
flag.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3400-rebase.sh                       | 10 +++++-----
 t/t3401-rebase-and-am-rename.sh         |  4 ++--
 t/t3404-rebase-interactive.sh           |  2 +-
 t/t3406-rebase-message.sh               | 12 ++++++------
 t/t3407-rebase-abort.sh                 |  6 +++---
 t/t3420-rebase-autostash.sh             |  2 +-
 t/t3425-rebase-topology-merges.sh       |  8 ++++----
 t/t3432-rebase-fast-forward.sh          |  4 ++--
 t/t3433-rebase-options-compatibility.sh |  8 ++++----
 t/t5407-post-rewrite-hook.sh            | 12 ++++++------
 t/t7512-status-help.sh                  | 12 ++++++------
 11 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 71fd6396cd..0a491f2363 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 
 test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --am -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase -f --onto init HEAD^ &&
+		test_must_fail git rebase --am -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index a0b9438b22..50803958fd 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --am: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --am A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ae6e55ce79..743b7e511a 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1137,7 +1137,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 0c2c569f95..7ce617fc1f 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --am master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --am topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase reflog-onto &&
+	git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 910f218284..3e31826170 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --am --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --am master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 5f7e73cf83..3816159e20 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index fd8efe84fe..19700b025b 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --am
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 40388ccf9f..4b3cecce56 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index bd4d2d2f63..a07e1f276b 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' '
 	new line 2
 	line 3
 	EOF
-	test_must_fail git rebase main side &&
+	test_must_fail git rebase --am main side &&
 	git rebase --abort &&
-	git rebase --ignore-whitespace main side &&
+	git rebase --am --ignore-whitespace main side &&
 	test_cmp expect file
 '
 
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' '
 
 test_expect_success '--committer-date-is-author-date works with am backend' '
 	git commit --amend &&
-	git rebase --committer-date-is-author-date HEAD^ &&
+	git rebase --am --committer-date-is-author-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	git show HEAD --pretty="format:%ci" >committertime &&
 	test_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' '
 # sets to +0530.
 test_expect_success '--ignore-date works with am backend' '
 	git commit --amend --date="$GIT_AUTHOR_DATE" &&
-	git rebase --ignore-date HEAD^ &&
+	git rebase --am --ignore-date HEAD^ &&
 	git show HEAD --pretty="format:%ai" >authortime &&
 	grep "+0000" authortime
 '
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 7344253bfb..a8a73616e4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --am' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --am --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --am --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --am --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 66d7a62797..d22b0acf2a 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --am in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --am in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --am conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 13/15] rebase tests: repeat some tests using the merge backend instead of am
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (11 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
                       ` (2 subsequent siblings)
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

In order to ensure the merge/interactive backend gets similar coverage
to the am one, add some tests for cases where previously only the am
backend was tested.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t5520-pull.sh                   | 17 +++++++++++++++--
 t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 602d996a33..3fff6a06fa 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -277,14 +277,27 @@ test_expect_success '--rebase' '
 	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=am pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index 860542aad0..d383ce8130 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '
 	)
 '
 
-test_expect_success 'rebase describes fake ancestor base' '
+test_expect_success 'rebase --merge describes parent of commit being picked' '
 	test_create_repo rebase &&
 	(
 		cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '
 		test_commit master file &&
 		git checkout -b side HEAD^ &&
 		test_commit side file &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --am describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
 		grep "||||||| constructed merge base" file
 	)
 '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 14/15] rebase: make the backend configurable via config setting
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (12 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2019-12-24 19:54     ` [PATCH v3 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  15 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt |  8 ++++++++
 builtin/rebase.c                | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d812..e6ae30c999 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,14 @@ rebase.useBuiltin::
 	is always used. Setting this will emit a warning, to alert any
 	remaining users that setting this now does nothing.
 
+rebase.backend::
+	Default backend to use for rebasing.  Possible choices are
+	'am' or 'merge' (note that the merge backend is sometimes also
+	refered to as the interactive backend or the interactive
+	machinery elsewhere in the docs).  Also, in the future, if the
+	merge backend gains all remaining capabilities of the am
+	backend, this setting may become unused.
+
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
 	rebase. False by default.
diff --git a/builtin/rebase.c b/builtin/rebase.c
index b7915fc0cb..d602b2da4c 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -60,6 +60,7 @@ enum empty_type {
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
+	const char *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -103,6 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
+		.default_backend = "am",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1298,6 +1300,10 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.backend")) {
+		return git_config_string(&opts->default_backend, var, value);
+	}
+
 	return git_default_config(var, value, data);
 }
 
@@ -1928,9 +1934,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
+		if (i >= 0) {
+			if (is_interactive(&options))
+				die(_("cannot combine am options with either "
+				      "interactive or merge options"));
+			else
+				options.type = REBASE_AM;
+		}
+	}
+
+	if (options.type == REBASE_UNSPECIFIED) {
+		if (!strcmp(options.default_backend, "merge"))
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "am"))
+			options.type = REBASE_AM;
+		else
+			die(_("Unknown rebase backend: %s"),
+			    options.default_backend);
 	}
 
 	switch (options.type) {
@@ -1943,10 +1963,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		options.state_dir = apply_dir();
 		break;
 	default:
-		/* the default rebase backend is `--am` */
-		options.type = REBASE_AM;
-		options.state_dir = apply_dir();
-		break;
+		BUG("options.type was just set above; should be unreachable.");
 	}
 
 	if (options.empty == EMPTY_UNSPECIFIED) {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (13 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
@ 2019-12-24 19:54     ` Elijah Newren via GitGitGadget
  2020-01-10 23:14       ` Jonathan Nieder
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  15 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2019-12-24 19:54 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Junio C Hamano, Elijah Newren

From: Elijah Newren <newren@gmail.com>

The am-backend drops information and thus limits what we can do:

  * lack of full tree information from the original commits means we
    cannot do directory rename detection and warn users that they might
    want to move some of their new files that they placed in old
    directories to prevent their becoming orphaned.[1]
  * reduction in context from only having a few lines beyond those
    changed means that when context lines are non-unique we can apply
    patches incorrectly.[2]
  * lack of access to original commits means that conflict marker
    annotation has less information available.

Also, the merge/interactive backend have far more abilities, appear to
currently have a slight performance advantage[3] and have room for more
optimizations than the am backend[4] (and work is underway to take
advantage of some of those possibilities).

[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt           |  2 +-
 builtin/rebase.c                       |  4 ++--
 t/t5520-pull.sh                        | 10 ++++++----
 t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index cf1ac2e359..e819889a31 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -309,7 +309,7 @@ See also INCOMPATIBLE OPTIONS below.
 --merge::
 	Use merging strategies to rebase.  When the recursive (default) merge
 	strategy is used, this allows rebase to be aware of renames on the
-	upstream side.
+	upstream side.  This is the default.
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
diff --git a/builtin/rebase.c b/builtin/rebase.c
index d602b2da4c..938a8840b1 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -104,7 +104,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "am",	  	\
+		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1945,7 +1945,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			options.type = REBASE_MERGE;
+			imply_interactive(&options, "--merge");
 		else if (!strcmp(options.default_backend, "am"))
 			options.type = REBASE_AM;
 		else
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3fff6a06fa..4f9e7f7ff6 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
 	(
 		cd dst &&
 		test_must_fail git pull --rebase &&
-		find .git/rebase-apply -name "000*" >patches &&
-		test_line_count = 1 patches
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d..aec45bca3b 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
  2019-12-24 19:54     ` [PATCH v3 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
@ 2020-01-07 14:37       ` Phillip Wood
  2020-01-07 19:15         ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-01-07 14:37 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Elijah Newren

Hi Elijah

Thanks for working on this series, I think making the sequencer the 
default backend is a good idea. I have a few reservations about this 
path though...

On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> Extend the interactive machinery with the ability to handle the full
> spread of options for how to handle commits that either start or become
> empty (by "become empty" I mean the changes in a commit are a subset of
> changes that exist upstream, so the net effect of applying the commit is
> no changes).  Introduce a new command line flag for selecting the
> desired behavior:
>     --empty={drop,keep,ask}
> with the definitions:
>     drop: drop empty commits
>     keep: keep empty commits
>     ask:  provide the user a chance to interact and pick what to do with
>           empty commits on a case-by-case basis

I think we want to distinguish between commits that are empty before
rebasing and those that become empty when they are rebased. --keep-empty 
explicily only applies to commits that are already empty. Cherry-pick
has distinct options for those two cases. If I've explicitly created an 
empty commit then I want to keep it but I don't want to keep commits 
that become empty because the changes they contain are already upstream.

If we want an option that keeps commits that become empty (Off hand I 
don't know why we would though) we should consider if that option should 
disable --cherry-mark when we create the todo list so that it keeps all 
commits that become empty when they're rebased.

> Note that traditionally, am-based rebases have always dropped commits
> that either started or became empty, while interactive-based rebases
> have defaulted to ask (and provided an option to keep commits that
> started empty).  This difference made sense since users of an am-based
> rebase just wanted to quickly batch apply a sequence of commits, while
> users editing a todo list will likely want the chance to interact and
> handle unusual cases on a case-by-case basis.  

I don't see why it makes sense to drop an empty commit that I've made 
just because it is being rebased. I'm pretty sure the behavor of the 
am-based rebase is a function of `git am` not being able to create empty 
commits.

> However, not all rebases
> using the interactive machinery are explicitly interactive anymore.  In
> particular --merge was always meant to behave more like --am: just
> rebase a batch of commits without popping up a todo list.
> 
> If the --empty flag is not specified, pick defaults as follows:
>     explicitly interactive: ask
>     --exec: keep (exec is about checking existing commits, and often
>                   used without actually changing the base.  Thus the
>                   expectation is that the user doesn't necessarily want
>                   anything to change; they just want to test).
>     otherwise: drop

I'm not sure I like changing the behavior based on --exec, I see what
you're getting at but it has the potential to be confusing. What if I
want to rearrange the commits without changing the base - why must I
specify --empty=keep there but not if I add --exec to the command line?

> Also, this commit makes --keep-empty just imply --empty=keep, and hides
> it from help so that we aren't confusing users with different ways to do
> the same thing.  (I could have added a --drop-empty flag, but then that
> invites users to specify both --keep-empty and --drop-empty and we have
> to add sanity checking around that; it seems cleaner to have a single
> multi-valued option.)  This actually fixes --keep-empty too; previously,
> it only meant to sometimes keep empty commits, in particular commits
> which started empty would be kept.  But it would still error out and ask
> the user what to do with commits that became empty.  Now it keeps empty
> commits, as instructed.

It certainly changes the behavior of --keep-empty but I'm not sure it 
"fixes" it. If I have some empty commits I want to keep as placeholders 
then that's different from wanting to keep commits that become empty 
because their changes are upstream but --cherry-mark didn't detect them.

In summary I'm in favor of making it easier to drop commits that become 
empty but not tying that to the handling of commits that are empty 
before they are rebased.

I'm also not happy that the deprecation of --keep-empty suddenly makes 
--no-keep-empty an error.

> 
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>  Documentation/git-rebase.txt      | 35 ++++++------
>  builtin/rebase.c                  | 83 +++++++++++++++++++++++++---
>  rebase-interactive.c              |  4 +-
>  rebase-interactive.h              |  2 +-
>  sequencer.c                       | 74 +++++++++++++++++++------
>  sequencer.h                       |  6 ++-
>  t/t3421-rebase-topology-linear.sh |  4 +-
>  t/t3424-rebase-empty.sh           | 89 +++++++++++++++++++++++++++++++
>  t/t3427-rebase-subtree.sh         | 16 +++---
>  9 files changed, 263 insertions(+), 50 deletions(-)
>  create mode 100755 t/t3424-rebase-empty.sh
> 
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index 1d0e2d27cc..ff32ca1080 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -258,9 +258,25 @@ See also INCOMPATIBLE OPTIONS below.
>  	original branch. The index and working tree are also left
>  	unchanged as a result.
>  
> +--empty={drop,keep,ask}::
> +	How to handle commits that become empty (because they contain a
> +	subset of already upstream changes) or start empty.  With drop
> +	(the default), commits that start or become empty are dropped.
> +	With keep (implied by --exec), such commits are kept.  With ask
> +	(implied by --interactive), the rebase will halt when an empty
> +	commit is applied allowing you to choose whether to drop it or
> +	commit it.  Also with ask, if the rebase is interactive then
> +	commits which start empty will be commented out in the todo
> +	action list (giving you a chance to uncomment).
> ++
> +Note that this has no effect on commits which are already upstream (as
> +can be checked via `git log --cherry-mark ...`), which are always
> +dropped by rebase.
> ++
> +See also INCOMPATIBLE OPTIONS below.
> +
>  --keep-empty::
> -	Keep the commits that do not change anything from its
> -	parents in the result.
> +	Deprecated alias for what is now known as --empty=keep.
>  +
>  See also INCOMPATIBLE OPTIONS below.
>  
> @@ -569,6 +585,7 @@ are incompatible with the following options:
>   * --interactive
>   * --exec
>   * --keep-empty
> + * --empty=
>   * --edit-todo
>   * --root when used in combination with --onto
>  
> @@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible:
>   * --preserve-merges and --ignore-whitespace
>   * --preserve-merges and --committer-date-is-author-date
>   * --preserve-merges and --ignore-date
> + * --preserve-merges and --empty=
>   * --keep-base and --onto
>   * --keep-base and --root
>  
> @@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES
>  
>  There are some subtle differences how the backends behave.
>  
> -Empty commits
> -~~~~~~~~~~~~~
> -
> -The am backend drops any "empty" commits, regardless of whether the
> -commit started empty (had no changes relative to its parent to
> -start with) or ended empty (all changes were already applied
> -upstream in other commits).
> -
> -The interactive backend drops commits by default that
> -started empty and halts if it hits a commit that ended up empty.
> -The `--keep-empty` option exists for the interactive backend to allow
> -it to keep commits that started empty.
> -
>  Directory rename detection
>  ~~~~~~~~~~~~~~~~~~~~~~~~~~
>  
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index ddf33bc9d4..6903249307 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -50,8 +50,16 @@ enum rebase_type {
>  	REBASE_PRESERVE_MERGES
>  };
>  
> +enum empty_type {
> +	EMPTY_UNSPECIFIED = -1,
> +	EMPTY_DROP,
> +	EMPTY_KEEP,
> +	EMPTY_ASK
> +};
> +
>  struct rebase_options {
>  	enum rebase_type type;
> +	enum empty_type empty;
>  	const char *state_dir;
>  	struct commit *upstream;
>  	const char *upstream_name;
> @@ -77,7 +85,6 @@ struct rebase_options {
>  	const char *action;
>  	int signoff;
>  	int allow_rerere_autoupdate;
> -	int keep_empty;
>  	int autosquash;
>  	int ignore_whitespace;
>  	char *gpg_sign_opt;
> @@ -95,6 +102,7 @@ struct rebase_options {
>  
>  #define REBASE_OPTIONS_INIT {			  	\
>  		.type = REBASE_UNSPECIFIED,	  	\
> +		.empty = EMPTY_UNSPECIFIED,	  	\
>  		.flags = REBASE_NO_QUIET, 		\
>  		.git_am_opts = ARGV_ARRAY_INIT,		\
>  		.git_format_patch_opt = STRBUF_INIT	\
> @@ -114,6 +122,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>  		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
>  	replay.allow_empty = 1;
>  	replay.allow_empty_message = opts->allow_empty_message;
> +	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
> +	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
> +	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
> +					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
>  	replay.verbose = opts->flags & REBASE_VERBOSE;
>  	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
>  	replay.committer_date_is_author_date =
> @@ -389,7 +401,10 @@ static int run_rebase_interactive(struct rebase_options *opts,
>  
>  	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
>  
> -	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
> +	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
> +	flags |= (opts->empty == EMPTY_ASK &&
> +		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
> +			TODO_LIST_ASK_EMPTY : 0;
>  	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
>  	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
>  	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
> @@ -453,6 +468,19 @@ static int run_rebase_interactive(struct rebase_options *opts,
>  	return ret;
>  }
>  
> +static int parse_opt_keep_empty(const struct option *opt, const char *arg,
> +				int unset)
> +{
> +	struct rebase_options *opts = opt->value;
> +
> +	BUG_ON_OPT_NEG(unset);
> +	BUG_ON_OPT_ARG(arg);
> +
> +	opts->empty = EMPTY_KEEP;
> +	opts->type = REBASE_INTERACTIVE;
> +	return 0;
> +}
> +
>  static const char * const builtin_rebase_interactive_usage[] = {
>  	N_("git rebase--interactive [<options>]"),
>  	NULL
> @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
>  	struct option options[] = {
>  		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
>  			   REBASE_FORCE),
> -		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
> +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> +			N_("(DEPRECATED) keep empty commits"),
> +			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,

It is all very well deprecating --keep-empty but suddenly making 
'--no-keep-empty' an error goes beyond deprecation. Also I'm not sure 
it's worth changing these options as I think the only user is 
git-rebase--preserve-merges.sh

Best Wishes

Phillip

> +			parse_opt_keep_empty },
>		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
>  			 N_("allow commits with empty messages")),
>  		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
> @@ -1166,7 +1197,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
>  		opts->allow_rerere_autoupdate ?
>  			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
>  			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
> -	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
> +	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
>  	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
>  	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
>  	add_var(&script_snippet, "cmd", opts->cmd);
> @@ -1360,6 +1391,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
>  	return 0;
>  }
>  
> +static enum empty_type parse_empty_value(const char *value)
> +{
> +	if (!strcasecmp(value, "drop"))
> +		return EMPTY_DROP;
> +	else if (!strcasecmp(value, "keep"))
> +		return EMPTY_KEEP;
> +	else if (!strcasecmp(value, "ask"))
> +		return EMPTY_ASK;
> +
> +	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
> +}
> +
> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +	enum empty_type value = parse_empty_value(arg);
> +
> +	BUG_ON_OPT_NEG(unset);
> +
> +	options->empty = value;
> +	return 0;
> +}
> +
>  static void NORETURN error_on_missing_default_upstream(void)
>  {
>  	struct branch *current_branch = branch_get(NULL);
> @@ -1505,8 +1559,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  				 "ignoring them"),
>  			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>  		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
> -		OPT_BOOL('k', "keep-empty", &options.keep_empty,
> -			 N_("preserve empty commits during rebase")),
> +		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
> +			       N_("how to handle empty commits"),
> +			       PARSE_OPT_NONEG, parse_opt_empty),
> +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> +			N_("(DEPRECATED) keep empty commits"),
> +			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
> +			parse_opt_keep_empty },
>  		OPT_BOOL(0, "autosquash", &options.autosquash,
>  			 N_("move commits that begin with "
>  			    "squash!/fixup! under -i")),
> @@ -1770,8 +1829,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	if (!(options.flags & REBASE_NO_QUIET))
>  		argv_array_push(&options.git_am_opts, "-q");
>  
> -	if (options.keep_empty)
> -		imply_interactive(&options, "--keep-empty");
> +	if (options.empty != EMPTY_UNSPECIFIED)
> +		imply_interactive(&options, "--empty");
>  
>  	if (gpg_sign) {
>  		free(options.gpg_sign_opt);
> @@ -1856,6 +1915,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  		break;
>  	}
>  
> +	if (options.empty == EMPTY_UNSPECIFIED) {
> +		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
> +			options.empty = EMPTY_ASK;
> +		else if (exec.nr > 0)
> +			options.empty = EMPTY_KEEP;
> +		else
> +			options.empty = EMPTY_DROP;
> +	}
>  	if (reschedule_failed_exec > 0 && !is_interactive(&options))
>  		die(_("--reschedule-failed-exec requires "
>  		      "--exec or --interactive"));
> diff --git a/rebase-interactive.c b/rebase-interactive.c
> index aa18ae82b7..ad82bf77df 100644
> --- a/rebase-interactive.c
> +++ b/rebase-interactive.c
> @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
>  	return MISSING_COMMIT_CHECK_IGNORE;
>  }
>  
> -void append_todo_help(unsigned keep_empty, int command_count,
> +void append_todo_help(unsigned no_ask_empty, int command_count,
>  		      const char *shortrevisions, const char *shortonto,
>  		      struct strbuf *buf)
>  {
> @@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,
>  
>  	strbuf_add_commented_lines(buf, msg, strlen(msg));
>  
> -	if (!keep_empty) {
> +	if (!no_ask_empty) {
>  		msg = _("Note that empty commits are commented out");
>  		strbuf_add_commented_lines(buf, msg, strlen(msg));
>  	}
> diff --git a/rebase-interactive.h b/rebase-interactive.h
> index 44dbb06311..f531e00ba7 100644
> --- a/rebase-interactive.h
> +++ b/rebase-interactive.h
> @@ -5,7 +5,7 @@ struct strbuf;
>  struct repository;
>  struct todo_list;
>  
> -void append_todo_help(unsigned keep_empty, int command_count,
> +void append_todo_help(unsigned no_ask_empty, int command_count,
>  		      const char *shortrevisions, const char *shortonto,
>  		      struct strbuf *buf);
>  int edit_todo_list(struct repository *r, struct todo_list *todo_list,
> diff --git a/sequencer.c b/sequencer.c
> index 763ccbbc45..d2c11f34b7 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -160,6 +160,9 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
>  static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
>  static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
>  static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
> +static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
>  
>  static int git_sequencer_config(const char *k, const char *v, void *cb)
>  {
> @@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r,
>  	empty_commit = is_original_commit_empty(commit);
>  	if (empty_commit < 0)
>  		return empty_commit;
> -	if (!empty_commit)
> +	if (!empty_commit || opts->ask_on_initially_empty)
>  		return 0;
>  	else
>  		return 1;
> @@ -1837,7 +1840,7 @@ static int do_pick_commit(struct repository *r,
>  	char *author = NULL;
>  	struct commit_message msg = { NULL, NULL, NULL, NULL };
>  	struct strbuf msgbuf = STRBUF_INIT;
> -	int res, unborn = 0, reword = 0, allow;
> +	int res, unborn = 0, reword = 0, allow, drop_commit;
>  
>  	if (opts->no_commit) {
>  		/*
> @@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r,
>  		goto leave;
>  	}
>  
> -	allow = allow_empty(r, opts, commit);
> -	if (allow < 0) {
> -		res = allow;
> -		goto leave;
> -	} else if (allow)
> -		flags |= ALLOW_EMPTY;
> -	if (!opts->no_commit) {
> +	drop_commit = 0;
> +	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
> +		drop_commit = 1;
> +		fprintf(stderr, _("No changes -- Patch already applied."));
> +	} else {
> +		allow = allow_empty(r, opts, commit);
> +		if (allow < 0) {
> +			res = allow;
> +			goto leave;
> +		} else if (allow) {
> +			flags |= ALLOW_EMPTY;
> +		}
> +	}
> +	if (!opts->no_commit && !drop_commit) {
>  		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
>  			res = do_commit(r, msg_file, author, opts, flags);
>  		else
> @@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)

This function is used by cherry-pick/revert not rebase do we need to
change it?

>  	else if (!strcmp(key, "options.allow-empty-message"))
>  		opts->allow_empty_message =
>  			git_config_bool_or_int(key, value, &error_flag);
> +	else if (!strcmp(key, "options.drop-redundant-commits"))
> +		opts->drop_redundant_commits =
> +			git_config_bool_or_int(key, value, &error_flag);
>  	else if (!strcmp(key, "options.keep-redundant-commits"))
>  		opts->keep_redundant_commits =
>  			git_config_bool_or_int(key, value, &error_flag);
> +	else if (!strcmp(key, "options.ask_on_initially_empty"))
> +		opts->ask_on_initially_empty =
> +			git_config_bool_or_int(key, value, &error_flag);>  	else if (!strcmp(key, "options.signoff"))
>  		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
>  	else if (!strcmp(key, "options.record-origin"))
> @@ -2612,6 +2628,15 @@ static int read_populate_opts(struct replay_opts *opts)
>  		if (file_exists(rebase_path_reschedule_failed_exec()))
>  			opts->reschedule_failed_exec = 1;
>  
> +		if (file_exists(rebase_path_drop_redundant_commits()))
> +			opts->drop_redundant_commits = 1;
> +
> +		if (file_exists(rebase_path_keep_redundant_commits()))
> +			opts->keep_redundant_commits = 1;
> +
> +		if (file_exists(rebase_path_ask_on_initially_empty()))
> +			opts->ask_on_initially_empty = 1;
> +
>  		read_strategy_opts(opts, &buf);
>  		strbuf_release(&buf);
>  
> @@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
>  		write_file(rebase_path_cdate_is_adate(), "%s", "");
>  	if (opts->ignore_date)
>  		write_file(rebase_path_ignore_date(), "%s", "");
> +	if (opts->drop_redundant_commits)
> +		write_file(rebase_path_drop_redundant_commits(), "%s", "");
> +	if (opts->keep_redundant_commits)
> +		write_file(rebase_path_keep_redundant_commits(), "%s", "");
> +	if (opts->ask_on_initially_empty)
> +		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
>  	if (opts->reschedule_failed_exec)
>  		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
>  
> @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)

again this is for cherry-pick/revert

>  	if (opts->allow_empty_message)
>  		res |= git_config_set_in_file_gently(opts_file,
>  				"options.allow-empty-message", "true");
> +	if (opts->drop_redundant_commits)
> +		res |= git_config_set_in_file_gently(opts_file,
> +				"options.drop-redundant-commits", "true");
>  	if (opts->keep_redundant_commits)
>  		res |= git_config_set_in_file_gently(opts_file,
>  				"options.keep-redundant-commits", "true");
> +	if (opts->ask_on_initially_empty)
> +		res |= git_config_set_in_file_gently(opts_file,
> +				"options.ask_on_initially_empty", "true");
>  	if (opts->signoff)
>  		res |= git_config_set_in_file_gently(opts_file,
>  					"options.signoff", "true");
> @@ -4691,7 +4728,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
>  				   struct rev_info *revs, struct strbuf *out,
>  				   unsigned flags)
>  {
> -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
> +	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
> +	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
>  	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
>  	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
>  	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
> @@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
>  		is_empty = is_original_commit_empty(commit);
>  		if (!is_empty && (commit->object.flags & PATCHSAME))
>  			continue;
> +		if (is_empty && drop_empty)
> +			continue;
>  
>  		strbuf_reset(&oneline);
>  		pretty_print_commit(pp, commit, &oneline);
> @@ -4754,7 +4794,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
>  		if (!to_merge) {
>  			/* non-merge commit: easy case */
>  			strbuf_reset(&buf);
> -			if (!keep_empty && is_empty)
> +			if (is_empty && ask_empty)
>  				strbuf_addf(&buf, "%c ", comment_line_char);
>  			strbuf_addf(&buf, "%s %s %s", cmd_pick,
>  				    oid_to_hex(&commit->object.oid),
> @@ -4922,7 +4962,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
>  	struct pretty_print_context pp = {0};
>  	struct rev_info revs;
>  	struct commit *commit;
> -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
> +	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
> +	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
>  	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
>  	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
>  
> @@ -4958,11 +4999,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
>  		return make_script_with_merges(&pp, &revs, out, flags);
>  
>  	while ((commit = get_revision(&revs))) {
> -		int is_empty  = is_original_commit_empty(commit);
> +		int is_empty = is_original_commit_empty(commit);
>  
>  		if (!is_empty && (commit->object.flags & PATCHSAME))
>  			continue;
> -		if (!keep_empty && is_empty)
> +		if (is_empty && drop_empty)
> +			continue;
> +		if (is_empty && ask_empty)
>  			strbuf_addf(out, "%c ", comment_line_char);
>  		strbuf_addf(out, "%s %s ", insn,
>  			    oid_to_hex(&commit->object.oid));
> @@ -5100,7 +5143,8 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
>  
>  	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
>  	if (flags & TODO_LIST_APPEND_TODO_HELP)
> -		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
> +		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
> +				 count_commands(todo_list),
>  				 shortrevisions, shortonto, &buf);
>  
>  	res = write_message(buf.buf, buf.len, file, 0);
> diff --git a/sequencer.h b/sequencer.h
> index e9a0e03ea2..1c3abb661c 100644
> --- a/sequencer.h
> +++ b/sequencer.h
> @@ -39,7 +39,9 @@ struct replay_opts {
>  	int allow_rerere_auto;
>  	int allow_empty;
>  	int allow_empty_message;
> +	int drop_redundant_commits;
>  	int keep_redundant_commits;
> +	int ask_on_initially_empty;
>  	int verbose;
>  	int quiet;
>  	int reschedule_failed_exec;
> @@ -134,7 +136,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
>  int sequencer_skip(struct repository *repo, struct replay_opts *opts);
>  int sequencer_remove_state(struct replay_opts *opts);
>  
> -#define TODO_LIST_KEEP_EMPTY (1U << 0)
> +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
>  #define TODO_LIST_SHORTEN_IDS (1U << 1)
>  #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
>  #define TODO_LIST_REBASE_MERGES (1U << 3)
> @@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);
>   * `--onto`, we do not want to re-generate the root commits.
>   */
>  #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
> +#define TODO_LIST_DROP_EMPTY (1U << 7)
> +#define TODO_LIST_ASK_EMPTY (1U << 8)
>  
>  
>  int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
> diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
> index 325072b0a3..d23e0bf778 100755
> --- a/t/t3421-rebase-topology-linear.sh
> +++ b/t/t3421-rebase-topology-linear.sh
> @@ -230,7 +230,7 @@ test_run_rebase () {
>  test_run_rebase success ''
>  test_run_rebase success -m
>  test_run_rebase success -i
> -test_have_prereq !REBASE_P || test_run_rebase failure -p
> +test_have_prereq !REBASE_P || test_run_rebase success -p
>  
>  test_run_rebase () {
>  	result=$1
> @@ -245,7 +245,7 @@ test_run_rebase () {
>  test_run_rebase success ''
>  test_run_rebase success -m
>  test_run_rebase success -i
> -test_have_prereq !REBASE_P || test_run_rebase failure -p
> +test_have_prereq !REBASE_P || test_run_rebase success -p
>  test_run_rebase success --rebase-merges
>  
>  #       m
> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
> new file mode 100755
> index 0000000000..9d52e1417f
> --- /dev/null
> +++ b/t/t3424-rebase-empty.sh
> @@ -0,0 +1,89 @@
> +#!/bin/sh
> +
> +test_description='git rebase of commits that start or become empty'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup test repository' '
> +	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
> +	test_write_lines A B C D E F G H I J >letters &&
> +	git add numbers letters &&
> +	git commit -m A &&
> +
> +	git branch upstream &&
> +	git branch localmods &&
> +
> +	git checkout upstream &&
> +	test_write_lines A B C D E >letters &&
> +	git add letters &&
> +	git commit -m B &&
> +
> +	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
> +	git add numbers &&
> +	git commit -m C &&
> +
> +	git checkout localmods &&
> +	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
> +	git add numbers &&
> +	git commit -m C2 &&
> +
> +	git commit --allow-empty -m D &&
> +
> +	test_write_lines A B C D E >letters &&
> +	git add letters &&
> +	git commit -m "Five letters ought to be enough for anybody"
> +'
> +
> +test_expect_success 'rebase --merge --empty=drop' '
> +	git checkout -B testing localmods &&
> +	git rebase --merge --empty=drop upstream &&
> +
> +	test_write_lines C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --merge --empty=keep' '
> +	git checkout -B testing localmods &&
> +	git rebase --merge --empty=keep upstream &&
> +
> +	test_write_lines D C2 C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --merge --empty=ask' '
> +	git checkout -B testing localmods &&
> +	test_must_fail git rebase --merge --empty=ask upstream &&
> +
> +	test_must_fail git rebase --skip &&
> +	git commit --allow-empty &&
> +	git rebase --continue &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
> +
> +test_expect_success 'rebase --interactive --empty=drop' '
> +	git checkout -B testing localmods &&
> +	git rebase --interactive --empty=drop upstream &&
> +
> +	test_write_lines C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --interactive --empty=keep' '
> +	git checkout -B testing localmods &&
> +	git rebase --interactive --empty=keep upstream &&
> +
> +	test_write_lines D C2 C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +
> +test_done
> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
> index bec48e6a1f..468ebc1bef 100755
> --- a/t/t3427-rebase-subtree.sh
> +++ b/t/t3427-rebase-subtree.sh
> @@ -85,23 +85,27 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
>  	verbose test "$(commit_message HEAD)" = "Empty commit"
>  '
>  
> -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
>  	reset_rebase &&
>  	git checkout -b rebase-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
>  	: first pick results in no changes &&
> -	git rebase --continue &&
> +	test_must_fail git rebase --skip &&
> +	: last pick was an empty commit that has no changes, but we want to keep it &&
> +	git commit --allow-empty &&
>  	verbose test "$(commit_message HEAD~2)" = "master4" &&
>  	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>  	verbose test "$(commit_message HEAD)" = "Empty commit"
>  '
>  
> -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
>  	reset_rebase &&
>  	git checkout -b rebase-merges-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
>  	: first pick results in no changes &&
> -	git rebase --continue &&
> +	test_must_fail git rebase --skip &&
> +	: last pick was an empty commit that has no changes, but we want to keep it &&
> +	git commit --allow-empty &&
>  	verbose test "$(commit_message HEAD~2)" = "master4" &&
>  	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>  	verbose test "$(commit_message HEAD)" = "Empty commit"
> 


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2019-12-24 19:54     ` [PATCH v3 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2020-01-07 14:43       ` Phillip Wood
  2020-01-07 19:26         ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-01-07 14:43 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, Elijah Newren

Hi Elijah

On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> Currently, this option doesn't do anything except error out if any
> options requiring the interactive-backend are also passed.  However,
> when we make the default backend configurable later in this series, this
> flag will provide a way to override the config setting.

I wonder if we really want to add a new option that we know we don't 
want to keep in the long term. Could we not just default to the 
interactive backend and fallback to the am backend if the user passes 
any of the am-based options on the commandline? i.e. reverse the current 
situation of defaulting to the am backend and selecting the interactive 
backend when the user passes certain options.

Best Wishes

Phillip


> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>   Documentation/git-rebase.txt | 11 ++++++++++-
>   builtin/rebase.c             | 18 +++++++++++++++++-
>   2 files changed, 27 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index f1ace07c38..cf1ac2e359 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
>   	original branch. The index and working tree are also left
>   	unchanged as a result.
>   
> +--am:
> +	Use git-am internally to rebase.  This option may become a
> +	no-op in the future once the interactive backend handles
> +	everything the am one does.
> ++
> +See also INCOMPATIBLE OPTIONS below.
> +
>   --empty={drop,keep,ask}::
>   	How to handle commits that become empty (because they contain a
>   	subset of already upstream changes) or start empty.  With drop
> @@ -372,7 +379,7 @@ See also INCOMPATIBLE OPTIONS below.
>   	Ensure at least <n> lines of surrounding context match before
>   	and after each change.  When fewer lines of surrounding
>   	context exist they all must match.  By default no context is
> -	ever ignored.
> +	ever ignored.  Implies --am.
>   +
>   See also INCOMPATIBLE OPTIONS below.
>   
> @@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch.
>   --whitespace=<option>::
>   	This flag is passed to the 'git apply' program
>   	(see linkgit:git-apply[1]) that applies the patch.
> +	Implies --am.
>   +
>   See also INCOMPATIBLE OPTIONS below.
>   
> @@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS
>   
>   The following options:
>   
> + * --am
>    * --whitespace
>    * -C
>   
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index d2b99e9908..b7915fc0cb 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -1361,6 +1361,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
>   	return res && is_linear_history(onto, head);
>   }
>   
> +static int parse_opt_am(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *opts = opt->value;
> +
> +	BUG_ON_OPT_NEG(unset);
> +	BUG_ON_OPT_ARG(arg);
> +
> +	opts->type = REBASE_AM;
> +
> +	return 0;
> +}
> +
>   /* -i followed by -m is still -i */
>   static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
>   {
> @@ -1546,6 +1558,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		OPT_CMDMODE(0, "show-current-patch", &action,
>   			    N_("show the patch file being applied or merged"),
>   			    ACTION_SHOW_CURRENT_PATCH),
> +		{ OPTION_CALLBACK, 0, "am", &options, NULL,
> +			N_("use apply-mail strategies to rebase"),
> +			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
> +			parse_opt_am },
>   		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
>   			N_("use merging strategies to rebase"),
>   			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
> @@ -1906,7 +1922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	if (isatty(2) && options.flags & REBASE_NO_QUIET)
>   		strbuf_addstr(&options.git_format_patch_opt, " --progress");
>   
> -	if (options.git_am_opts.argc) {
> +	if (options.git_am_opts.argc || options.type == REBASE_AM) {
>   		/* all am options except -q are compatible only with --am */
>   		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
>   			if (strcmp(options.git_am_opts.argv[i], "-q"))
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
  2020-01-07 14:37       ` Phillip Wood
@ 2020-01-07 19:15         ` Elijah Newren
  2020-01-08 14:27           ` Phillip Wood
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-01-07 19:15 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi Phillip,

On Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Elijah
>
> Thanks for working on this series, I think making the sequencer the
> default backend is a good idea. I have a few reservations about this
> path though...

Thanks for the feedback.  You've provided some good food for thought.
While responding, I came up with an alternate proposal below which I
think may be more in line with what you want.  Let me know...

>
> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > Extend the interactive machinery with the ability to handle the full
> > spread of options for how to handle commits that either start or become
> > empty (by "become empty" I mean the changes in a commit are a subset of
> > changes that exist upstream, so the net effect of applying the commit is
> > no changes).  Introduce a new command line flag for selecting the
> > desired behavior:
> >     --empty={drop,keep,ask}
> > with the definitions:
> >     drop: drop empty commits
> >     keep: keep empty commits
> >     ask:  provide the user a chance to interact and pick what to do with
> >           empty commits on a case-by-case basis
>
> I think we want to distinguish between commits that are empty before
> rebasing and those that become empty when they are rebased. --keep-empty
> explicily only applies to commits that are already empty. Cherry-pick

I am open to changing how the empty handling works; I figured it was
the most likely area people might have feedback about.

However, I do strongly disagree with the "explicit" claim here.  It is
possible that --keep-empty was designed to only apply to commits that
begin empty, but that is not at all explicit.  The only place in the
documentation I could find that mentions the distinction at all was in
the "Behavioral Differences" section, which was written by me as part
of commit  0661e49aeb84 ("git-rebase.txt: document behavioral
differences between modes", 2018-06-27).  Quoting from that commit
message:

"(In fact, it's not clear -- to me at least -- that these differences
were even desirable or intentional.)  Document these differences."

I cannot find anywhere else making a distinction in either the
documentation or the commit messages or even any code comments, which
has left me wondering whether the distinction was intentional at all.
Things like commit b00bf1c9a8dd ("git-rebase: make
--allow-empty-message the default", 2018-06-27) give heavy precedence
to the assumption that git-rebase is often more happenstance than
design when it comes to edge cases like these.

> has distinct options for those two cases. If I've explicitly created an
> empty commit then I want to keep it but I don't want to keep commits
> that become empty because the changes they contain are already upstream.
>
> If we want an option that keeps commits that become empty (Off hand I
> don't know why we would though) we should consider if that option should
> disable --cherry-mark when we create the todo list so that it keeps all
> commits that become empty when they're rebased.

To answer the particular reason I have seen why folks might want to
keep both commits that start empty and become empty, but still use
--cherry-mark to discard commits that are already upstream:
  * If cherry-mark can determine the commit was "already upstream",
then because of how cherry-mark works this means the upstream commit
message was about the *exact* same set of changes.  Thus, the commit
messages can be assumed to be fully interchangeable (and are in fact
likely to be completely identical).
  * If a commit does not start empty, but becomes so because its
changes were a subset of those upstream, then while there are clearly
no more changes that need to be applied, the commit messages can be
expected to differ -- either there will be an upstream commit that
included a bunch of other changes too, or the commit we are rebasing
will be spread across multiple upstream commits.  If the project has
bad commit message hygiene (because e.g. they are using atrocious code
review tools like GitHub that don't allow you to review them), there's
a reasonable likelihood that the upstream version only talks about the
other changes in the commit instead of the fix we are rebasing.  If
so, there may be useful information in this commit message that we
want to copy somewhere.

It's a somewhat rare usecase involving projects that generally aren't
careful with commit messages but which has some people working on it
who are careful.  But even for these projects, the odds that a commit
message will just get tossed anyway seem fairly high, so I will not
strongly defend this choice; it just seemed like "--keep-empty" or
"--empty=keep" should mean "keep empty", not "sometimes keep empty".
So, if it'd be nice if we could come up with clear wording if we are
going to implement other possibilities.

Anyway, am I correctly understanding that you would like to see the
following matrix of options?
  * Drop commits that either start or become empty [the current --empty=drop]
  * Pause and ask users whether they want commits that either start or
become empty [the current --empty=ask]
  * Keep commits that start empty, but drop ones that become empty [a
new option]
and possibly drop the current --empty=keep behavior?

> > Note that traditionally, am-based rebases have always dropped commits
> > that either started or became empty, while interactive-based rebases
> > have defaulted to ask (and provided an option to keep commits that
> > started empty).  This difference made sense since users of an am-based
> > rebase just wanted to quickly batch apply a sequence of commits, while
> > users editing a todo list will likely want the chance to interact and
> > handle unusual cases on a case-by-case basis.
>
> I don't see why it makes sense to drop an empty commit that I've made
> just because it is being rebased.

Are you arguing that keeping commits that started empty should
actually be the default, i.e. that all three rebase backends have
traditionally had the wrong default?

> I'm pretty sure the behavor of the
> am-based rebase is a function of `git am` not being able to create empty
> commits.

That may be, but it's not quite clear.  Junio kind of commented on
this in https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/.
His comments that "lack of --keep-empty on the 'am' side is probably a
bug that wants to be fixed" might imply that he only sees it as a
useful option.  If the behavior of the am-based rebase dropping
commits which started empty was solely a function of git-am not being
able to create empty commits, then the correct fix would be to make
keeping the commits which started empty as the default or maybe only
behavior rather than just making it an option.

I'd actually be open to changing the default here.  We have a
precedent in doing so with commit b00bf1c9a8dd ("git-rebase: make
--allow-empty-message the default", 2018-06-27) where we stated that
really rare edge cases (which I think both empty commit messages and
commits which start empty qualify as) probably have the wrong default
in rebase.  And changing the default would yield a much easier to
understand matrix of options:

  * Drop commits that become empty (modification to --empty=drop in
that it keeps commits which started empty)
  * Pause and ask users if commits become empty (modification to
--empty=ask in that it keeps commits which started empty)
  * Keep commits that become empty, in addition to those that started
empty (--empty=keep).

> > However, not all rebases
> > using the interactive machinery are explicitly interactive anymore.  In
> > particular --merge was always meant to behave more like --am: just
> > rebase a batch of commits without popping up a todo list.
> >
> > If the --empty flag is not specified, pick defaults as follows:
> >     explicitly interactive: ask
> >     --exec: keep (exec is about checking existing commits, and often
> >                   used without actually changing the base.  Thus the
> >                   expectation is that the user doesn't necessarily want
> >                   anything to change; they just want to test).
> >     otherwise: drop
>
> I'm not sure I like changing the behavior based on --exec, I see what
> you're getting at but it has the potential to be confusing. What if I
> want to rearrange the commits without changing the base - why must I
> specify --empty=keep there but not if I add --exec to the command line?

This was probably poorly explained and also poorly implemented.  Let
me try from a slightly different angle, and come to a slightly
different result than I did previously:

Quoting from Junio in b00bf1c9a8dd ("git-rebase: make
--allow-empty-message the default", 2018-06-27),
   '"am" based rebase is solely to transplant an existing history and
want to stop much less than "interactive" one whose purpose is to
polish a series before making it publishable, and asking for
confirmation ("this has become empty--do you want to drop it?") is
more appropriate from the workflow point of view'

I would modify Junio's wording to expand "am based rebase" to say that
if the user hasn't requested an interactive rebase or otherwise
explicitly signalled that they want some measure of interactivity
(e.g. specified --empty=ask), then we should not stop and ask how to
handle commits which started OR became empty.  In other worse, it's
not just the am-based rebase that is about transplanting commits but
any rebase where interactivity isn't requested.  I think the
traditional --keep-empty is quite broken in this regard since it does
stop and ask with commits that become empty.  rebase -m, rebase
--exec, etc. should not stop when a commit becomes empty, there should
be some default it just uses unless the user also adds
--interactive/-i/--empty=ask.

But, if we take your above suggestion to make keeping the commits
which started empty not only the default but remove it from the
options, then this becomes much easier.  The choices become (kind of
repeated from above):

[Keep the commits that started empty in all three cases, otherwise:]
  * Drop commits that become empty (--empty=drop)
  * Pause and ask users if commits become empty (--empty=ask)
  * Keep commits that become empty (--empty=keep)

and the defaults are simply:
   --interactive/-i: --empty=ask
   everything else: --empty=drop

(And as a bonus, in the thread I've referenced twice above Junio
mentioned -- in respect to commits that become empty -- that he thinks
these are the right defaults.  He didn't comment on the behavior of
commits that start empty, which seems to be your primary concern.)

> > Also, this commit makes --keep-empty just imply --empty=keep, and hides
> > it from help so that we aren't confusing users with different ways to do
> > the same thing.  (I could have added a --drop-empty flag, but then that
> > invites users to specify both --keep-empty and --drop-empty and we have
> > to add sanity checking around that; it seems cleaner to have a single
> > multi-valued option.)  This actually fixes --keep-empty too; previously,
> > it only meant to sometimes keep empty commits, in particular commits
> > which started empty would be kept.  But it would still error out and ask
> > the user what to do with commits that became empty.  Now it keeps empty
> > commits, as instructed.
>
> It certainly changes the behavior of --keep-empty but I'm not sure it
> "fixes" it. If I have some empty commits I want to keep as placeholders
> then that's different from wanting to keep commits that become empty
> because their changes are upstream but --cherry-mark didn't detect them.
>
> In summary I'm in favor of making it easier to drop commits that become
> empty but not tying that to the handling of commits that are empty
> before they are rebased.

Sounds like my modified proposal above may be in line with your tastes?

> I'm also not happy that the deprecation of --keep-empty suddenly makes
> --no-keep-empty an error.

We could translate that to --empty=drop; does that sound reasonable?

[...]
> > @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
> >       struct option options[] = {
> >               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
> >                          REBASE_FORCE),
> > -             OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
> > +             { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> > +                     N_("(DEPRECATED) keep empty commits"),
> > +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
>
> It is all very well deprecating --keep-empty but suddenly making
> '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure
> it's worth changing these options as I think the only user is
> git-rebase--preserve-merges.sh

Side track: Since git-rebase--preserve-merges.sh is deprecated and we
want to get rid of it, and rebase-merges exists and is a better
implementation of the original idea, can we just translate rebase -p
into rebase -r and delete git-rebase--preserve-merges.sh?  (With a few
wrinkles, such as telling users in the middle of an existing
preserve-merges-rebase that they either need to use an old version of
git to continue their rebase or else abort the rebase?)

> Best Wishes
>
> Phillip
>
[...]
> This function is used by cherry-pick/revert not rebase do we need to
> change it?

Possibly not; I can take a look.  I had quite a difficult time trying
to figure out how to make sure that --empty options were saved and
restored across rebase --continue; it's not at all unlikely that I
modified more than I needed to "for consistency".

[...]
> > @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
>
> again this is for cherry-pick/revert

Okay, will double check this too.

[...]

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2020-01-07 14:43       ` Phillip Wood
@ 2020-01-07 19:26         ` Elijah Newren
  2020-01-07 20:11           ` Junio C Hamano
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-01-07 19:26 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi Phillip,

On Tue, Jan 7, 2020 at 6:43 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Elijah
>
> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > Currently, this option doesn't do anything except error out if any
> > options requiring the interactive-backend are also passed.  However,
> > when we make the default backend configurable later in this series, this
> > flag will provide a way to override the config setting.
>
> I wonder if we really want to add a new option that we know we don't
> want to keep in the long term. Could we not just default to the
> interactive backend and fallback to the am backend if the user passes
> any of the am-based options on the commandline? i.e. reverse the current
> situation of defaulting to the am backend and selecting the interactive
> backend when the user passes certain options.

Good question.  For context, this option served a few purposes:
  * Providing an escape hatch during the transition in case the merge
backend doesn't handle everything the am one does in our first
attempts (i.e. similar to how we allowed using the scripted rebase
even after providing a built-in one).
  * Makes the documentation much more concise and clear (being able to
specify that various flags "imply --am" is simple, whereas removing
the flag makes me feel I need the full description of --am in each of
the few places a flag that would have implied it).
  * Provides an easy way to make the plethora of am-specific rebase
tests use the am-backend.

However, I thought of this option before Junio suggested a
rebase.backend config setting, so we could just rely on that instead.
Thus, getting rid of the "--am" flag in detail would mean:
  * I need to redo the test changes in this series to use "-c
rebase.backend=am" instead of "--am"
  * It will be slightly harder for users to use the escape hatch in
one-off cases during the transition
  * We need to figure out the right way to reword the documentation

The first two are pretty minor, so that probably means I just need to
come up with some good wording for the documentation (suggestions
welcome...)


Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2020-01-07 19:26         ` Elijah Newren
@ 2020-01-07 20:11           ` Junio C Hamano
  2020-01-08 14:32             ` Phillip Wood
  0 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-01-07 20:11 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Phillip Wood, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Pavel Roskin, Alban Gruin,
	SZEDER Gábor

Elijah Newren <newren@gmail.com> writes:

> However, I thought of this option before Junio suggested a
> rebase.backend config setting, so we could just rely on that instead.
> Thus, getting rid of the "--am" flag in detail would mean:
>   * I need to redo the test changes in this series to use "-c
> rebase.backend=am" instead of "--am"
>   * It will be slightly harder for users to use the escape hatch in
> one-off cases during the transition
>   * We need to figure out the right way to reword the documentation
>
> The first two are pretty minor, so that probably means I just need to
> come up with some good wording for the documentation (suggestions
> welcome...)

It probably is a good idea to keep --am (and possibly --[no-]am) as
long as rebase.backend option exists.  A configuration variable that
changes behaviour without allowing it to be overridden from the
command line is not a good idea.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
  2020-01-07 19:15         ` Elijah Newren
@ 2020-01-08 14:27           ` Phillip Wood
  2020-01-09 21:32             ` Johannes Schindelin
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-01-08 14:27 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi Elijah

Thanks for such a detailed reply, I'll try and do it justice.

TLDR I like your new proposal

On 07/01/2020 19:15, Elijah Newren wrote:
> Hi Phillip,
> 
> On Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> Hi Elijah
>>
>> Thanks for working on this series, I think making the sequencer the
>> default backend is a good idea. I have a few reservations about this
>> path though...
> 
> Thanks for the feedback.  You've provided some good food for thought.
> While responding, I came up with an alternate proposal below which I
> think may be more in line with what you want.  Let me know...
> 
>>
>> On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
>>> From: Elijah Newren <newren@gmail.com>
>>>
>>> Extend the interactive machinery with the ability to handle the full
>>> spread of options for how to handle commits that either start or become
>>> empty (by "become empty" I mean the changes in a commit are a subset of
>>> changes that exist upstream, so the net effect of applying the commit is
>>> no changes).  Introduce a new command line flag for selecting the
>>> desired behavior:
>>>     --empty={drop,keep,ask}
>>> with the definitions:
>>>     drop: drop empty commits
>>>     keep: keep empty commits
>>>     ask:  provide the user a chance to interact and pick what to do with
>>>           empty commits on a case-by-case basis
>>
>> I think we want to distinguish between commits that are empty before
>> rebasing and those that become empty when they are rebased. --keep-empty
>> explicily only applies to commits that are already empty. Cherry-pick
> 
> I am open to changing how the empty handling works; I figured it was
> the most likely area people might have feedback about.
> 
> However, I do strongly disagree with the "explicit" claim here.  It is
> possible that --keep-empty was designed to only apply to commits that
> begin empty, but that is not at all explicit.

Maybe explicit wasn't the best choice of words. I was going off the
implementation which takes pains to only allow and empty commit when the
original was empty. This has been the case since the feature was
introduced in 90e1818f9a ("git-rebase: add keep_empty flag",
2012-04-20). I did some digging in the mail archive this morning and
found [1] which seems to confirm the intention was to preserve existing
empty commits, not keep those that become empty. This fits with the
discussion [2] which lead to this feature. The same patch series that
introduced --keep-empty for rebase introduced the two cherry-pick flags
--allow-empty and --keep-redundant-commits to distinguish between the
two cases and used `cherry-pick --allow-empty` to implement `rebase
--keep-empty` (and failed to provide a way to continue after stopping 
for a conflict resolution)

[1] https://lore.kernel.org/git/5006614E.8090601@viscovery.net/
[2]
https://lore.kernel.org/git/20120323185205.GA11916@hmsreliant.think-freely.org/

>  The only place in the
> documentation I could find that mentions the distinction at all was in
> the "Behavioral Differences" section, which was written by me as part
> of commit  0661e49aeb84 ("git-rebase.txt: document behavioral
> differences between modes", 2015-06-27).  Quoting from that commit
> message:
> 
> "(In fact, it's not clear -- to me at least -- that these differences
> were even desirable or intentional.)  Document these differences."
> 
> I cannot find anywhere else making a distinction in either the
> documentation or the commit messages or even any code comments, which
> has left me wondering whether the distinction was intentional at all.
> Things like commit b00bf1c9a8dd ("git-rebase: make
> --allow-empty-message the default", 2018-06-27) give heavy precedence
> to the assumption that git-rebase is often more happenstance than
> design when it comes to edge cases like these.
> 
>> has distinct options for those two cases. If I've explicitly created an
>> empty commit then I want to keep it but I don't want to keep commits
>> that become empty because the changes they contain are already upstream.
>>
>> If we want an option that keeps commits that become empty (Off hand I
>> don't know why we would though) we should consider if that option should
>> disable --cherry-mark when we create the todo list so that it keeps all
>> commits that become empty when they're rebased.
> 
> To answer the particular reason I have seen why folks might want to
> keep both commits that start empty and become empty, but still use
> --cherry-mark to discard commits that are already upstream:
>   * If cherry-mark can determine the commit was "already upstream",
> then because of how cherry-mark works this means the upstream commit
> message was about the *exact* same set of changes.  Thus, the commit
> messages can be assumed to be fully interchangeable (and are in fact
> likely to be completely identical).
>   * If a commit does not start empty, but becomes so because its
> changes were a subset of those upstream, then while there are clearly
> no more changes that need to be applied, the commit messages can be
> expected to differ -- either there will be an upstream commit that
> included a bunch of other changes too, or the commit we are rebasing
> will be spread across multiple upstream commits.  If the project has
> bad commit message hygiene (because e.g. they are using atrocious code
> review tools like GitHub that don't allow you to review them), there's
> a reasonable likelihood that the upstream version only talks about the
> other changes in the commit instead of the fix we are rebasing.  If
> so, there may be useful information in this commit message that we
> want to copy somewhere.

That sounds reasonable, (I'm not sure what one would do with the commit
message though as creating an empty commit wont be any use for blame or
someone looking at the commit that introduced the change upstream. One
could use add a note to the upstream commit but then there's the problem 
of sharing notes)

> It's a somewhat rare usecase involving projects that generally aren't
> careful with commit messages but which has some people working on it
> who are careful.  But even for these projects, the odds that a commit
> message will just get tossed anyway seem fairly high, so I will not
> strongly defend this choice; it just seemed like "--keep-empty" or
> "--empty=keep" should mean "keep empty", not "sometimes keep empty".
> So, if it'd be nice if we could come up with clear wording if we are
> going to implement other possibilities.
> 
> Anyway, am I correctly understanding that you would like to see the
> following matrix of options?
>   * Drop commits that either start or become empty [the current --empty=drop]

Yes please

>   * Pause and ask users whether they want commits that either start or
> become empty [the current --empty=ask]

I'd prefer an option to keep the commits that start empty and ask about
the ones that become empty (just like --keep-empty does now)

>   * Keep commits that start empty, but drop ones that become empty [a
> new option]

Yes please

> and possibly drop the current --empty=keep behavior?

Yes as I'm not sure it's that useful, if users want to keep the
information from the message of a commit that becomes empty I'm not sure
just creating an empty commit is going to be much use in practice.

>>> Note that traditionally, am-based rebases have always dropped commits
>>> that either started or became empty, while interactive-based rebases
>>> have defaulted to ask (and provided an option to keep commits that
>>> started empty).  This difference made sense since users of an am-based
>>> rebase just wanted to quickly batch apply a sequence of commits, while
>>> users editing a todo list will likely want the chance to interact and
>>> handle unusual cases on a case-by-case basis.
>>
>> I don't see why it makes sense to drop an empty commit that I've made
>> just because it is being rebased.
> 
> Are you arguing that keeping commits that started empty should
> actually be the default, i.e. that all three rebase backends have
> traditionally had the wrong default?

Yes, I think rebase should preserve the commits that are being rebased
as far as possible and the current default is probably a historical accident

>> I'm pretty sure the behavor of the
>> am-based rebase is a function of `git am` not being able to create empty
>> commits.
> 
> That may be, but it's not quite clear.  Junio kind of commented on
> this in https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/.
> His comments that "lack of --keep-empty on the 'am' side is probably a
> bug that wants to be fixed" might imply that he only sees it as a
> useful option.  If the behavior of the am-based rebase dropping
> commits which started empty was solely a function of git-am not being
> able to create empty commits, then the correct fix would be to make
> keeping the commits which started empty as the default or maybe only
> behavior rather than just making it an option.

Yeah I'm not sure how Junio feels defaulting to keeping commits that
start empty

> I'd actually be open to changing the default here.

I'd be keen to do that but we should try and find out what others think.

>  We have a
> precedent in doing so with commit b00bf1c9a8dd ("git-rebase: make
> --allow-empty-message the default", 2018-06-27) where we stated that
> really rare edge cases (which I think both empty commit messages and
> commits which start empty qualify as) probably have the wrong default
> in rebase.  And changing the default would yield a much easier to
> understand matrix of options:
> 
>   * Drop commits that become empty (modification to --empty=drop in
> that it keeps commits which started empty)
>   * Pause and ask users if commits become empty (modification to
> --empty=ask in that it keeps commits which started empty)
>   * Keep commits that become empty, in addition to those that started
> empty (--empty=keep).

That matrix is easier to understand and covers all the cases I'm 
interested in.

>>> However, not all rebases
>>> using the interactive machinery are explicitly interactive anymore.  In
>>> particular --merge was always meant to behave more like --am: just
>>> rebase a batch of commits without popping up a todo list.
>>>
>>> If the --empty flag is not specified, pick defaults as follows:
>>>     explicitly interactive: ask
>>>     --exec: keep (exec is about checking existing commits, and often
>>>                   used without actually changing the base.  Thus the
>>>                   expectation is that the user doesn't necessarily want
>>>                   anything to change; they just want to test).
>>>     otherwise: drop
>>
>> I'm not sure I like changing the behavior based on --exec, I see what
>> you're getting at but it has the potential to be confusing. What if I
>> want to rearrange the commits without changing the base - why must I
>> specify --empty=keep there but not if I add --exec to the command line?
> 
> This was probably poorly explained and also poorly implemented.  Let
> me try from a slightly different angle, and come to a slightly
> different result than I did previously:
> 
> Quoting from Junio in b00bf1c9a8dd ("git-rebase: make
> --allow-empty-message the default", 2018-06-27),
>    '"am" based rebase is solely to transplant an existing history and
> want to stop much less than "interactive" one whose purpose is to
> polish a series before making it publishable, and asking for
> confirmation ("this has become empty--do you want to drop it?") is
> more appropriate from the workflow point of view'
> 
> I would modify Junio's wording to expand "am based rebase" to say that
> if the user hasn't requested an interactive rebase or otherwise
> explicitly signalled that they want some measure of interactivity
> (e.g. specified --empty=ask), then we should not stop and ask how to
> handle commits which started OR became empty.  In other worse, it's
> not just the am-based rebase that is about transplanting commits but
> any rebase where interactivity isn't requested.  I think the
> traditional --keep-empty is quite broken in this regard since it does
> stop and ask with commits that become empty.  rebase -m, rebase
> --exec, etc. should not stop when a commit becomes empty, there should
> be some default it just uses unless the user also adds
> --interactive/-i/--empty=ask.
> 
> But, if we take your above suggestion to make keeping the commits
> which started empty not only the default but remove it from the
> options, then this becomes much easier.  The choices become (kind of
> repeated from above):
> 
> [Keep the commits that started empty in all three cases, otherwise:]
>   * Drop commits that become empty (--empty=drop)
>   * Pause and ask users if commits become empty (--empty=ask)
>   * Keep commits that become empty (--empty=keep)

That's nice - I like it

> and the defaults are simply:
>    --interactive/-i: --empty=ask
>    everything else: --empty=drop

While I see the point about the difference between -i and the other 
rebase modes any flavor of rebase can stop at any time due to conflicts 
so it can never be fully automatic. Given that commits becoming empty is 
a bit of a corner case I'm not sure the complexity of different defaults 
is justified but I don't feel that strongly about it.

> (And as a bonus, in the thread I've referenced twice above Junio
> mentioned -- in respect to commits that become empty -- that he thinks
> these are the right defaults.  He didn't comment on the behavior of
> commits that start empty, which seems to be your primary concern.)

>>> Also, this commit makes --keep-empty just imply --empty=keep, and hides
>>> it from help so that we aren't confusing users with different ways to do
>>> the same thing.  (I could have added a --drop-empty flag, but then that
>>> invites users to specify both --keep-empty and --drop-empty and we have
>>> to add sanity checking around that; it seems cleaner to have a single
>>> multi-valued option.)  This actually fixes --keep-empty too; previously,
>>> it only meant to sometimes keep empty commits, in particular commits
>>> which started empty would be kept.  But it would still error out and ask
>>> the user what to do with commits that became empty.  Now it keeps empty
>>> commits, as instructed.
>>
>> It certainly changes the behavior of --keep-empty but I'm not sure it
>> "fixes" it. If I have some empty commits I want to keep as placeholders
>> then that's different from wanting to keep commits that become empty
>> because their changes are upstream but --cherry-mark didn't detect them.
>>
>> In summary I'm in favor of making it easier to drop commits that become
>> empty but not tying that to the handling of commits that are empty
>> before they are rebased.
> 
> Sounds like my modified proposal above may be in line with your tastes?

Yes. Keep commits that start empty and have an option to control what 
happens to those that become empty (defaulting to --empty=ask for 
interactive rebases at least).

>> I'm also not happy that the deprecation of --keep-empty suddenly makes
>> --no-keep-empty an error.
> 
> We could translate that to --empty=drop; does that sound reasonable?

translating it to --empty=ask would keep the behavior the same even if 
it sounds a bit odd. I don't think we want to change the behavior of 
--[no-]keep-empty while it is deprecated.

> 
> [...]
>>> @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
>>>       struct option options[] = {
>>>               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
>>>                          REBASE_FORCE),
>>> -             OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
>>> +             { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>>> +                     N_("(DEPRECATED) keep empty commits"),
>>> +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
>>
>> It is all very well deprecating --keep-empty but suddenly making
>> '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure
>> it's worth changing these options as I think the only user is
>> git-rebase--preserve-merges.sh
> 
> Side track: Since git-rebase--preserve-merges.sh is deprecated and we
> want to get rid of it, and rebase-merges exists and is a better
> implementation of the original idea, can we just translate rebase -p
> into rebase -r and delete git-rebase--preserve-merges.sh?  (With a few
> wrinkles, such as telling users in the middle of an existing
> preserve-merges-rebase that they either need to use an old version of
> git to continue their rebase or else abort the rebase?)

dscho posted some patches in November or December moving along this 
path, I'm not sure what the outcome was. It's a bit complicated by the 
different todo list formats between -p and -r but I think the end game 
should be to treat -p as -r as you suggest.

> 
>> Best Wishes
>>
>> Phillip
>>
> [...]
>> This function is used by cherry-pick/revert not rebase do we need to
>> change it?
> 
> Possibly not; I can take a look.  I had quite a difficult time trying
> to figure out how to make sure that --empty options were saved and
> restored across rebase --continue; it's not at all unlikely that I
> modified more than I needed to "for consistency".
> 
> [...]
>>> @@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
>>
>> again this is for cherry-pick/revert
> 
> Okay, will double check this too.
> 
> [...]
> 


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2020-01-07 20:11           ` Junio C Hamano
@ 2020-01-08 14:32             ` Phillip Wood
  2020-01-08 17:18               ` Junio C Hamano
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-01-08 14:32 UTC (permalink / raw)
  To: Junio C Hamano, Elijah Newren
  Cc: Phillip Wood, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Pavel Roskin, Alban Gruin,
	SZEDER Gábor

On 07/01/2020 20:11, Junio C Hamano wrote:
> Elijah Newren <newren@gmail.com> writes:
> 
>> However, I thought of this option before Junio suggested a
>> rebase.backend config setting, so we could just rely on that instead.
>> Thus, getting rid of the "--am" flag in detail would mean:
>>    * I need to redo the test changes in this series to use "-c
>> rebase.backend=am" instead of "--am"
>>    * It will be slightly harder for users to use the escape hatch in
>> one-off cases during the transition
>>    * We need to figure out the right way to reword the documentation
>>
>> The first two are pretty minor, so that probably means I just need to
>> come up with some good wording for the documentation (suggestions
>> welcome...)
> 
> It probably is a good idea to keep --am (and possibly --[no-]am) as
> long as rebase.backend option exists.  A configuration variable that
> changes behaviour without allowing it to be overridden from the
> command line is not a good idea.

I view this change in the default backend as similar to the rewrite in C 
in that it is an implementation detail we're changing that should be 
transparent (and beneficial in terms of performance) to the user. There 
we provided a configuration variable but not a command line option to 
control if it was used or not. I don't see it as something the user will 
want to change from rebase to rebase but perhaps I'm missing something.

Best Wishes

Phillip

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2020-01-08 14:32             ` Phillip Wood
@ 2020-01-08 17:18               ` Junio C Hamano
  2020-01-08 18:55                 ` Phillip Wood
  0 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-01-08 17:18 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren, Phillip Wood, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Denton Liu, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Phillip Wood <phillip.wood123@gmail.com> writes:

> I view this change in the default backend as similar to the rewrite in
> C in that it is an implementation detail we're changing that should be
> transparent (and beneficial in terms of performance) to the
> user. There we provided a configuration variable but not a command
> line option to control if it was used or not.

Do you mean things like GIT_TEST_ADD_I_USE_BUILTIN?  I think it is
OK to have such an escape mechanism to allow people to opt out of
a new reimplementation until it matures, but I do not offhand recall
doing so with config.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 10/15] rebase: add an --am option
  2020-01-08 17:18               ` Junio C Hamano
@ 2020-01-08 18:55                 ` Phillip Wood
  0 siblings, 0 replies; 161+ messages in thread
From: Phillip Wood @ 2020-01-08 18:55 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Elijah Newren, Phillip Wood, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Denton Liu, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

On 08/01/2020 17:18, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> I view this change in the default backend as similar to the rewrite in
>> C in that it is an implementation detail we're changing that should be
>> transparent (and beneficial in terms of performance) to the
>> user. There we provided a configuration variable but not a command
>> line option to control if it was used or not.
> 
> Do you mean things like GIT_TEST_ADD_I_USE_BUILTIN?  I think it is
> OK to have such an escape mechanism to allow people to opt out of
> a new reimplementation until it matures, but I do not offhand recall
> doing so with config.

I was thinking of rebase.usebuiltin

Best Wishes

Phillip

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
  2020-01-08 14:27           ` Phillip Wood
@ 2020-01-09 21:32             ` Johannes Schindelin
  0 siblings, 0 replies; 161+ messages in thread
From: Johannes Schindelin @ 2020-01-09 21:32 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren, Elijah Newren via GitGitGadget, Git Mailing List,
	Phillip Wood, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi Phillip,

On Wed, 8 Jan 2020, Phillip Wood wrote:

> On 07/01/2020 19:15, Elijah Newren wrote:
> >
> > On Tue, Jan 7, 2020 at 6:37 AM Phillip Wood <phillip.wood123@gmail.com>
> > wrote:
> >
> > > On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
> > >
> > [...]
> > > > @@ -466,7 +494,10 @@ int cmd_rebase__interactive(int argc, const char
> > > > **argv, const char *prefix)
> > > >       struct option options[] = {
> > > >               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
> > > >                          REBASE_FORCE),
> > > > -             OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty
> > > > commits")),
> > > > +             { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> > > > +                     N_("(DEPRECATED) keep empty commits"),
> > > > +                     PARSE_OPT_NOARG | PARSE_OPT_NONEG |
> > > > PARSE_OPT_HIDDEN,
> > >
> > > It is all very well deprecating --keep-empty but suddenly making
> > > '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure
> > > it's worth changing these options as I think the only user is
> > > git-rebase--preserve-merges.sh
> >
> > Side track: Since git-rebase--preserve-merges.sh is deprecated and we
> > want to get rid of it, and rebase-merges exists and is a better
> > implementation of the original idea, can we just translate rebase -p
> > into rebase -r and delete git-rebase--preserve-merges.sh?  (With a few
> > wrinkles, such as telling users in the middle of an existing
> > preserve-merges-rebase that they either need to use an old version of
> > git to continue their rebase or else abort the rebase?)
>
> dscho posted some patches in November or December moving along this path, I'm
> not sure what the outcome was. It's a bit complicated by the different todo
> list formats between -p and -r but I think the end game should be to treat -p
> as -r as you suggest.

I sent some patches to switch `git svn` over to `rebase -r`, but that was
only in non-interactive mode.

In interactive mode, the todo lists indeed look too different and I think
we'll just need to wait a year or so and then we can just delete
`git-rebase--preserve-merges.sh`.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2019-12-24 19:54     ` [PATCH v3 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2020-01-10 23:14       ` Jonathan Nieder
  2020-01-11  1:16         ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-10 23:14 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, gitster,
	plroskin, alban.gruin, szeder.dev, Elijah Newren

Hi,

Elijah Newren via GitGitGadget wrote:

> The am-backend drops information and thus limits what we can do:
>
>   * lack of full tree information from the original commits means we
>     cannot do directory rename detection and warn users that they might
>     want to move some of their new files that they placed in old
>     directories to prevent their becoming orphaned.[1]
>   * reduction in context from only having a few lines beyond those
>     changed means that when context lines are non-unique we can apply
>     patches incorrectly.[2]
>   * lack of access to original commits means that conflict marker
>     annotation has less information available.
>
> Also, the merge/interactive backend have far more abilities, appear to
> currently have a slight performance advantage[3] and have room for more
> optimizations than the am backend[4] (and work is underway to take
> advantage of some of those possibilities).
>
> [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
> [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
> [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
> [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/
>
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>  Documentation/git-rebase.txt           |  2 +-
>  builtin/rebase.c                       |  4 ++--
>  t/t5520-pull.sh                        | 10 ++++++----
>  t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
>  4 files changed, 11 insertions(+), 8 deletions(-)

Thanks for writing this.  We finally rolled this out to our internal
population at $DAYJOB and ran into a couple of issues:

 1. "git rebase --am" does not invoke the post-commit hook, but "git
    rebase --merge" does.  Is this behavior change intended?

    Noticed because jiri[1] installs a post-commit hook that warns
    about commits on detached HEAD, so this change makes rebases more
    noisy in repositories that were set up using jiri.

 2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is
    not interactive.

 3. In circumstances I haven't pinned down yet, we get the error
    message "invalid date format: @@2592000 +0000":

	$ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L
	$ git checkout --theirs file
	$ git add file
	$ git rebase --continue
	fatal: invalid date format: @@2592000 +0000
	error: could not commit staged changes.

    This isn't reproducible without --committer-date-is-author-date.
    More context (the test where it happens) is in [2].

 4. I suspect the exit status in the "you need to resolve conflicts"
    case has changed.  With rebase --am, [3] would automatically
    invoke rebase --abort when conflicts are present, but with rebase
    --merge it does not.

Known?

Thanks,
Jonathan

[1] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/manifest.go#1347
[2] https://source.chromium.org/chromium/chromium/tools/depot_tools/+/master:tests/git_common_test.py;l=721;drc=6b52dc21e166c46707b4c8eb26c74c70d4f9977e;bpv=1;bpt=0
[3] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/project.go#1664

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-10 23:14       ` Jonathan Nieder
@ 2020-01-11  1:16         ` Elijah Newren
  2020-01-11 14:41           ` Phillip Wood
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-01-11  1:16 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi Jonathan,

On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Hi,
>
> Elijah Newren via GitGitGadget wrote:
>
> > The am-backend drops information and thus limits what we can do:
> >
> >   * lack of full tree information from the original commits means we
> >     cannot do directory rename detection and warn users that they might
> >     want to move some of their new files that they placed in old
> >     directories to prevent their becoming orphaned.[1]
> >   * reduction in context from only having a few lines beyond those
> >     changed means that when context lines are non-unique we can apply
> >     patches incorrectly.[2]
> >   * lack of access to original commits means that conflict marker
> >     annotation has less information available.
> >
> > Also, the merge/interactive backend have far more abilities, appear to
> > currently have a slight performance advantage[3] and have room for more
> > optimizations than the am backend[4] (and work is underway to take
> > advantage of some of those possibilities).
> >
> > [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
> > [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
> > [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
> > [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/
> >
> > Signed-off-by: Elijah Newren <newren@gmail.com>
> > ---
> >  Documentation/git-rebase.txt           |  2 +-
> >  builtin/rebase.c                       |  4 ++--
> >  t/t5520-pull.sh                        | 10 ++++++----
> >  t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
> >  4 files changed, 11 insertions(+), 8 deletions(-)
>
> Thanks for writing this.  We finally rolled this out to our internal
> population at $DAYJOB and ran into a couple of issues:

Cool, thanks for testing it out.

>  1. "git rebase --am" does not invoke the post-commit hook, but "git
>     rebase --merge" does.  Is this behavior change intended?
>
>     Noticed because jiri[1] installs a post-commit hook that warns
>     about commits on detached HEAD, so this change makes rebases more
>     noisy in repositories that were set up using jiri.

I've never used a post-commit hook or seen one in the wild.  Certainly
wasn't intentional, but it's not clear to me if it's wrong or right
either.  I don't see why it would make sense to distinguish between
any of git rebase --am/--merge/--interactive, but it isn't too
surprising that by historical accident the two rebase backends which
happened to call git-commit behind the scenes would call a post-commit
hook and the other rebase backend that didn't call git-commit
wouldn't.

But the big question here, is what is correct behavior?  Should rebase
call the post-commit hook, or should it skip it?  I haven't any clue
what the answer to that is.

>  2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is
>     not interactive.

Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of
rebases which use the interactive machinery even if they aren't
explicitly interactive.  I've never seen the "-i" in the reflog
message defined, but clearly it has always been used whenever the
interactive machinery was in play regardless of whether the rebase was
interactive.  In that regard, I figured that --merge fit in rather
nicely.  (And I noted the fact that reflog messages were different
between the backends among the "BEHAVIORAL DIFFERENCES" section of
git-rebase.txt).  But if others think we should just drop the -i (much
as we did for the bash prompt), I'd be happy with that too.  If we go
that route, I think I'd rather drop the -i in the reflog for all
rebases, not just the
using-the-interactive-machinery-but-not-explicitly-interactive ones.

>  3. In circumstances I haven't pinned down yet, we get the error
>     message "invalid date format: @@2592000 +0000":
>
>         $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L
>         $ git checkout --theirs file
>         $ git add file
>         $ git rebase --continue
>         fatal: invalid date format: @@2592000 +0000
>         error: could not commit staged changes.
>
>     This isn't reproducible without --committer-date-is-author-date.
>     More context (the test where it happens) is in [2].

Interesting.  Do you happen to know if this started happening with
ra/rebase-i-more-options, or did it just become an issue with
en/rebase-backend?  I looked around at the link you provided and feel
a bit confused; I'm not sure which test does this or how I'd
reproduce.

>  4. I suspect the exit status in the "you need to resolve conflicts"
>     case has changed.  With rebase --am, [3] would automatically
>     invoke rebase --abort when conflicts are present, but with rebase
>     --merge it does not.
>
> Known?

Nope, but I would certainly hope that "you need to resolve conflicts"
would result in a non-zero exit status.  If it doesn't, that sounds
like a bug in the interactive backend that we need to fix.  I'll dig
in.



Thanks for the reports!
Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-11  1:16         ` Elijah Newren
@ 2020-01-11 14:41           ` Phillip Wood
  2020-01-12 17:59             ` Johannes Schindelin
                               ` (2 more replies)
  0 siblings, 3 replies; 161+ messages in thread
From: Phillip Wood @ 2020-01-11 14:41 UTC (permalink / raw)
  To: Elijah Newren, Jonathan Nieder
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi Jonathan & Elijah

On 11/01/2020 01:16, Elijah Newren wrote:
> Hi Jonathan,
> 
> On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>>
>> Hi,
>>
>> Elijah Newren via GitGitGadget wrote:
>>
>>> The am-backend drops information and thus limits what we can do:
>>>
>>>    * lack of full tree information from the original commits means we
>>>      cannot do directory rename detection and warn users that they might
>>>      want to move some of their new files that they placed in old
>>>      directories to prevent their becoming orphaned.[1]
>>>    * reduction in context from only having a few lines beyond those
>>>      changed means that when context lines are non-unique we can apply
>>>      patches incorrectly.[2]
>>>    * lack of access to original commits means that conflict marker
>>>      annotation has less information available.
>>>
>>> Also, the merge/interactive backend have far more abilities, appear to
>>> currently have a slight performance advantage[3] and have room for more
>>> optimizations than the am backend[4] (and work is underway to take
>>> advantage of some of those possibilities).
>>>
>>> [1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
>>> [2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
>>> [3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
>>> [4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/
>>>
>>> Signed-off-by: Elijah Newren <newren@gmail.com>
>>> ---
>>>   Documentation/git-rebase.txt           |  2 +-
>>>   builtin/rebase.c                       |  4 ++--
>>>   t/t5520-pull.sh                        | 10 ++++++----
>>>   t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
>>>   4 files changed, 11 insertions(+), 8 deletions(-)
>>
>> Thanks for writing this.  We finally rolled this out to our internal
>> population at $DAYJOB and ran into a couple of issues:
> 
> Cool, thanks for testing it out.
> 
>>   1. "git rebase --am" does not invoke the post-commit hook, but "git
>>      rebase --merge" does.  Is this behavior change intended?
>>
>>      Noticed because jiri[1] installs a post-commit hook that warns
>>      about commits on detached HEAD, so this change makes rebases more
>>      noisy in repositories that were set up using jiri.

Perhaps that hook could learn not to warn if a branch is being rebased? 
git could be more helpful there by having a porcelain option to status 
that prints the branch name if we're rebasing (`git worktree --list` 
shows the branch correctly when it's being rebased but does not (yet - I 
have a patch to do it) mark the current worktree so isn't very helpful.)

> I've never used a post-commit hook or seen one in the wild.  Certainly
> wasn't intentional, but it's not clear to me if it's wrong or right
> either.  I don't see why it would make sense to distinguish between
> any of git rebase --am/--merge/--interactive, but it isn't too
> surprising that by historical accident the two rebase backends which
> happened to call git-commit behind the scenes would call a post-commit
> hook and the other rebase backend that didn't call git-commit
> wouldn't.

Looking through the history the am based rebase has never run the 
post-commit hook as am has its own set of hooks and the scripted version 
used commit-tree. The merge based rebase ran `git commit` which ran the 
post commit hook. The interactive rebase ran the hook until and I broke 
it in a356ee4659b ("sequencer: try to commit without forking 'git 
commit'", 2017-11-24) and after I fixed it in 4627bc777e ("sequencer: 
run post-commit hook", 2019-10-15). As it was broken for two years with 
no one noticing it can't be that popular.

> But the big question here, is what is correct behavior?  Should rebase
> call the post-commit hook, or should it skip it?  I haven't any clue
> what the answer to that is.

It's creating a new commit so I lean towards thinking it should run the 
post-commit hook. As an example I have a post-commit hook that prints a 
warning if a commit is created on a branch that is being rewritten by 
one of my scripts in another worktree. There are pre-commit and 
pre-rebase hooks to try and prevent that, but the warning is there as a 
last resort if those hooks are by-passed.

>>   2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is
>>      not interactive.

If this is important to people I think it should be easy enough to set 
GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long 
as it hasn't already been set by the user) rather than relying on 
sequencer.c to do it.

> Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of
> rebases which use the interactive machinery even if they aren't
> explicitly interactive.  I've never seen the "-i" in the reflog
> message defined, but clearly it has always been used whenever the
> interactive machinery was in play regardless of whether the rebase was
> interactive.  In that regard, I figured that --merge fit in rather
> nicely.  (And I noted the fact that reflog messages were different
> between the backends among the "BEHAVIORAL DIFFERENCES" section of
> git-rebase.txt).  But if others think we should just drop the -i (much
> as we did for the bash prompt), I'd be happy with that too.  If we go
> that route, I think I'd rather drop the -i in the reflog for all
> rebases, not just the
> using-the-interactive-machinery-but-not-explicitly-interactive ones.
> 
>>   3. In circumstances I haven't pinned down yet, we get the error
>>      message "invalid date format: @@2592000 +0000":
>>
>>          $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L
>>          $ git checkout --theirs file
>>          $ git add file
>>          $ git rebase --continue
>>          fatal: invalid date format: @@2592000 +0000
>>          error: could not commit staged changes.
>>
>>      This isn't reproducible without --committer-date-is-author-date.
>>      More context (the test where it happens) is in [2].
> 
> Interesting.  Do you happen to know if this started happening with
> ra/rebase-i-more-options, or did it just become an issue with
> en/rebase-backend?  I looked around at the link you provided and feel
> a bit confused; I'm not sure which test does this or how I'd
> reproduce.

I'm confused by the test as well. As ra/rebase-i-more-options only 
touched the sequencer then any bugs would only show up in this test 
(which runs a non-interactive rebase) once en/rbease-backend switched to 
that backend. It seems likely that ra/rebase-i-more-options is to blame.

Jonathan - do you happen to know if your users create empty commits at 
all? and if so what do they expect rebase to do with them (and any that 
become empty when they are rebased) - cf 
https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>

Best Wishes

Phillip

>>   4. I suspect the exit status in the "you need to resolve conflicts"
>>      case has changed.  With rebase --am, [3] would automatically
>>      invoke rebase --abort when conflicts are present, but with rebase
>>      --merge it does not.
>>
>> Known?
> 
> Nope, but I would certainly hope that "you need to resolve conflicts"
> would result in a non-zero exit status.  If it doesn't, that sounds
> like a bug in the interactive backend that we need to fix.  I'll dig
> in.
> 
> 
> 
> Thanks for the reports!
> Elijah
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-11 14:41           ` Phillip Wood
@ 2020-01-12 17:59             ` Johannes Schindelin
  2020-01-16  6:32               ` Elijah Newren
  2020-01-12 21:23             ` Junio C Hamano
  2020-01-15 19:50             ` Jonathan Nieder
  2 siblings, 1 reply; 161+ messages in thread
From: Johannes Schindelin @ 2020-01-12 17:59 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren, Jonathan Nieder, Elijah Newren via GitGitGadget,
	Git Mailing List, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi,

On Sat, 11 Jan 2020, Phillip Wood wrote:

> On 11/01/2020 01:16, Elijah Newren wrote:
> >
> > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
> > >
> > > Elijah Newren via GitGitGadget wrote:
> > >
> > >   1. "git rebase --am" does not invoke the post-commit hook, but "git
> > >      rebase --merge" does.  Is this behavior change intended?
> > >
> > >      Noticed because jiri[1] installs a post-commit hook that warns
> > >      about commits on detached HEAD, so this change makes rebases more
> > >      noisy in repositories that were set up using jiri.
>
> Perhaps that hook could learn not to warn if a branch is being rebased?
> git could be more helpful there by having a porcelain option to status
> that prints the branch name if we're rebasing (`git worktree --list`
> shows the branch correctly when it's being rebased but does not (yet - I
> have a patch to do it) mark the current worktree so isn't very helpful.)
>
> > I've never used a post-commit hook or seen one in the wild.  Certainly
> > wasn't intentional, but it's not clear to me if it's wrong or right
> > either.  I don't see why it would make sense to distinguish between
> > any of git rebase --am/--merge/--interactive, but it isn't too
> > surprising that by historical accident the two rebase backends which
> > happened to call git-commit behind the scenes would call a post-commit
> > hook and the other rebase backend that didn't call git-commit
> > wouldn't.
>
> Looking through the history the am based rebase has never run the post-commit
> hook as am has its own set of hooks and the scripted version used commit-tree.
> The merge based rebase ran `git commit` which ran the post commit hook. The
> interactive rebase ran the hook until and I broke it in a356ee4659b
> ("sequencer: try to commit without forking 'git commit'", 2017-11-24) and
> after I fixed it in 4627bc777e ("sequencer: run post-commit hook",
> 2019-10-15). As it was broken for two years with no one noticing it can't be
> that popular.

Maybe a crazy idea, but maybe not: how about running the `post-commit`
hook _only_ if `--merge` was specified explicitly, and in that case (and
guarded behind a check verifying that the `post-commit` hook _actually_
exists _and_ is executable) warn the user that this hook won't be run in
future versions?

To make things better for users who actually want to run that hook during
rebases, we could introduce a config option, say,
`rebase.runPostCommitHook` that is a tri-state (`true`, `false`,
`onlyForDashDashMerge`, at first defaulting to the last, eventually to
`false`).

Crazy? Or helpful?

> > But the big question here, is what is correct behavior?  Should rebase
> > call the post-commit hook, or should it skip it?  I haven't any clue
> > what the answer to that is.
>
> It's creating a new commit so I lean towards thinking it should run the
> post-commit hook. As an example I have a post-commit hook that prints a
> warning if a commit is created on a branch that is being rewritten by
> one of my scripts in another worktree. There are pre-commit and
> pre-rebase hooks to try and prevent that, but the warning is there as a
> last resort if those hooks are by-passed.

I guess you're right, it is quite surprising that the `post-commit` hook
is _not_ run for `--am` rebases even though commits are created.

> > >   2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is
> > >      not interactive.
>
> If this is important to people I think it should be easy enough to set
> GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long
> as it hasn't already been set by the user) rather than relying on
> sequencer.c to do it.

I agree (but won't have time to implement it, so maybe I should shut up
already...)

> > Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of
> > rebases which use the interactive machinery even if they aren't
> > explicitly interactive.  I've never seen the "-i" in the reflog
> > message defined, but clearly it has always been used whenever the
> > interactive machinery was in play regardless of whether the rebase was
> > interactive.  In that regard, I figured that --merge fit in rather
> > nicely.  (And I noted the fact that reflog messages were different
> > between the backends among the "BEHAVIORAL DIFFERENCES" section of
> > git-rebase.txt).  But if others think we should just drop the -i (much
> > as we did for the bash prompt), I'd be happy with that too.  If we go
> > that route, I think I'd rather drop the -i in the reflog for all
> > rebases, not just the
> > using-the-interactive-machinery-but-not-explicitly-interactive ones.
> >
> > >   3. In circumstances I haven't pinned down yet, we get the error
> > >      message "invalid date format: @@2592000 +0000":
> > >
> > >          $ git rebase --committer-date-is-author-date --onto branch_K
> > >          branch_L~1 branch_L
> > >          $ git checkout --theirs file
> > >          $ git add file
> > >          $ git rebase --continue
> > >          fatal: invalid date format: @@2592000 +0000
> > >          error: could not commit staged changes.
> > >
> > >      This isn't reproducible without --committer-date-is-author-date.
> > >      More context (the test where it happens) is in [2].
> >
> > Interesting.  Do you happen to know if this started happening with
> > ra/rebase-i-more-options, or did it just become an issue with
> > en/rebase-backend?  I looked around at the link you provided and feel
> > a bit confused; I'm not sure which test does this or how I'd
> > reproduce.
>
> I'm confused by the test as well. As ra/rebase-i-more-options only touched the
> sequencer then any bugs would only show up in this test (which runs a
> non-interactive rebase) once en/rbease-backend switched to that backend. It
> seems likely that ra/rebase-i-more-options is to blame.
>
> Jonathan - do you happen to know if your users create empty commits at all?
> and if so what do they expect rebase to do with them (and any that become
> empty when they are rebased) - cf
> https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>

The double `@` looks very funny. I would be interested in seeing an MCVE.

> > >   4. I suspect the exit status in the "you need to resolve conflicts"
> > >      case has changed.  With rebase --am, [3] would automatically
> > >      invoke rebase --abort when conflicts are present, but with rebase
> > >      --merge it does not.
> > >
> > > Known?
> >
> > Nope, but I would certainly hope that "you need to resolve conflicts"
> > would result in a non-zero exit status.  If it doesn't, that sounds
> > like a bug in the interactive backend that we need to fix.  I'll dig
> > in.

Yes, exiting with status 0 would be a major bug, and I think it might even
be a bug that was introduced by me when I re-implemented the core loop of
the interactive rebase in C.

But to me it sounds as if 4. is not so about the exit code but about
aborting immediately. I do not recall seeing --am rebases to abort,
though, but to exit with error (and I saw the same behavior in interactive
rebases).

We will need to see a reduced concrete example (preferably as a new test
case) of the described behavior.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-11 14:41           ` Phillip Wood
  2020-01-12 17:59             ` Johannes Schindelin
@ 2020-01-12 21:23             ` Junio C Hamano
  2020-01-15 19:50             ` Jonathan Nieder
  2 siblings, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2020-01-12 21:23 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren, Jonathan Nieder, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Phillip Wood <phillip.wood123@gmail.com> writes:

> Looking through the history the am based rebase has never run the
> post-commit hook as am has its own set of hooks and the scripted
> version used commit-tree.
>
>> But the big question here, is what is correct behavior?  Should rebase
>> call the post-commit hook, or should it skip it?  I haven't any clue
>> what the answer to that is.
>
> It's creating a new commit so I lean towards thinking it should run
> the post-commit hook. As an example I have a post-commit hook that

The reason why "am" did not run "post-commit" is because
"post-commit" was (originally) meant to be for cases where the end
user types "git commit", IOW, the notion of "since we are creating a
new commit object, let's run the post-commit hook" used to be
totally misguided way of thinking.  The hook was "the user _ran_
'commit', so post-commit is run immediately afterwards".

These days, I think most of our tools subscribe to the newer "a
commit got created--have hook the chance to report it" idea, simply
because the original position is untenable---"git merge" may not
want to run the post-commit hook according to the initial design,
and it is fine to make it not to run it when clean automerge was
recorded, but sometimes the end users have to type "git commit" to
conclude a conflicted merge.

So from that point of view, I do not think it is too bad to bring
"am" into the new ear and start running post-commit from it.

The "pre-commit" hook is a different story, though.  From purely
practical point of view, starting to run extra verification that may
veto new commits from getting created only because we changed the
implementation detail for some reason is a disservice to the users.


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-11 14:41           ` Phillip Wood
  2020-01-12 17:59             ` Johannes Schindelin
  2020-01-12 21:23             ` Junio C Hamano
@ 2020-01-15 19:50             ` Jonathan Nieder
  2020-01-15 21:59               ` Emily Shaffer
  2 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-15 19:50 UTC (permalink / raw)
  To: phillip.wood
  Cc: Elijah Newren, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi,

Phillip Wood wrote:
> On 11/01/2020 01:16, Elijah Newren wrote:

>> But the big question here, is what is correct behavior?  Should rebase
>> call the post-commit hook, or should it skip it?  I haven't any clue
>> what the answer to that is.
>
> It's creating a new commit so I lean towards thinking it should run the
> post-commit hook. As an example I have a post-commit hook that prints a
> warning if a commit is created on a branch that is being rewritten by one of
> my scripts in another worktree. There are pre-commit and pre-rebase hooks to
> try and prevent that, but the warning is there as a last resort if those
> hooks are by-passed.

The initial design for the post-commit hook was

    commit 89e2c5f17b901edf28a8bb778ece3f6c18bbb721
    Author: Junio C Hamano <gitster@pobox.com>
    Date:   Thu Aug 18 17:20:08 2005 -0700

    Add commit hook and make the verification customizable.

    There are three hooks:

        - 'pre-commit' is given an opportunity to inspect what is
          being committed, before we invoke the EDITOR for the
          commit message;

        - 'commit-msg' is invoked on the commit log message after
          the user prepares it;

        - 'post-commit' is run after a successful commit is made.

    The first two can interfere to stop the commit.  The last one is
    for after-the-fact notification.

The initial implementation was only in "git commit" and didn't affect
other commands, but that's an artifact of implementation, not
intention.  The intention is more murky: certainly "creating a new
commit" is not the event we want to notify about (for example, "git
commit-tree" should not invoke the hook), but if I were designing it
today then all operations that create a new commit and update the
current branch with it might qualify.

Even that definition is a bit fuzzy: when I run "git rebase <upstream>
<branch>", am I updating <branch> or the current branch?  Are
cherry-picks that carry over changes that were committed previously
new commits?

Worse, we have the body of existing post-commit hooks to contend with,
and each one may have made different assumptions about the semantics.
We do not have the luxury of designing the hook without regard to that
body of existing hooks today.

The hook that jiri installs is very simple.  It wants to check when a
user has (interactively) made a commit on a detached HEAD, to let them
know that they might want to use "git checkout -b" afterward.  With
this particular hook, the behavior is better when git rebase does not
invoke the hook, since in the context of a rebase, the user has no
need to use "git checkout -b" afterward.  This is a regression, and I
think we need to take that seriously.

It doesn't seem like jiri is doing anything weird here, so there's a
reasonably high probability that other hooks would be affected in the
same way.  How can we help authors of such hooks to handle this
change?

I don't think that starting to invoke the 'post-commit' hook even more
without going through that thought process is an acceptable fix.
Since the "right" semantics aren't obvious here, the first step is
probably to collect some typical examples of post-commit hooks, like
the example from the message I'm replying to (thank you!).  Ideas for
where we can find more?

Thanks,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-15 19:50             ` Jonathan Nieder
@ 2020-01-15 21:59               ` Emily Shaffer
  0 siblings, 0 replies; 161+ messages in thread
From: Emily Shaffer @ 2020-01-15 21:59 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: phillip.wood, Elijah Newren, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

On Wed, Jan 15, 2020 at 07:50:11PM +0000, Jonathan Nieder wrote:
> Hi,
> 
> Phillip Wood wrote:
> > On 11/01/2020 01:16, Elijah Newren wrote:
> 
> >> But the big question here, is what is correct behavior?  Should rebase
> >> call the post-commit hook, or should it skip it?  I haven't any clue
> >> what the answer to that is.
> >
> > It's creating a new commit so I lean towards thinking it should run the
> > post-commit hook. As an example I have a post-commit hook that prints a
> > warning if a commit is created on a branch that is being rewritten by one of
> > my scripts in another worktree. There are pre-commit and pre-rebase hooks to
> > try and prevent that, but the warning is there as a last resort if those
> > hooks are by-passed.
> 
> The initial design for the post-commit hook was
> 
>     commit 89e2c5f17b901edf28a8bb778ece3f6c18bbb721
>     Author: Junio C Hamano <gitster@pobox.com>
>     Date:   Thu Aug 18 17:20:08 2005 -0700
> 
>     Add commit hook and make the verification customizable.
> 
>     There are three hooks:
> 
>         - 'pre-commit' is given an opportunity to inspect what is
>           being committed, before we invoke the EDITOR for the
>           commit message;
> 
>         - 'commit-msg' is invoked on the commit log message after
>           the user prepares it;
> 
>         - 'post-commit' is run after a successful commit is made.
> 
>     The first two can interfere to stop the commit.  The last one is
>     for after-the-fact notification.
> 
> The initial implementation was only in "git commit" and didn't affect
> other commands, but that's an artifact of implementation, not
> intention.  The intention is more murky: certainly "creating a new
> commit" is not the event we want to notify about (for example, "git
> commit-tree" should not invoke the hook), but if I were designing it
> today then all operations that create a new commit and update the
> current branch with it might qualify.
> 
> Even that definition is a bit fuzzy: when I run "git rebase <upstream>
> <branch>", am I updating <branch> or the current branch?  Are
> cherry-picks that carry over changes that were committed previously
> new commits?
> 
> Worse, we have the body of existing post-commit hooks to contend with,
> and each one may have made different assumptions about the semantics.
> We do not have the luxury of designing the hook without regard to that
> body of existing hooks today.

This seems like the most important point to me. I took a very cursory
peek at some search results on Github
(https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code)
and I see mostly shims - make sure certain files were included in the
commit, bring a worktree up to date, stage stuff that wasn't committed -
but again, that's a very brief glance at over 19000 probable post-commit
hooks. Of course it's not possible for us to examine all of them, but
having these 19k scripts being run lots of times when they didn't used
to during an operation as common as rebase seems a little scary.

> The hook that jiri installs is very simple.  It wants to check when a
> user has (interactively) made a commit on a detached HEAD, to let them
> know that they might want to use "git checkout -b" afterward.  With
> this particular hook, the behavior is better when git rebase does not
> invoke the hook, since in the context of a rebase, the user has no
> need to use "git checkout -b" afterward.  This is a regression, and I
> think we need to take that seriously.
> 
> It doesn't seem like jiri is doing anything weird here, so there's a
> reasonably high probability that other hooks would be affected in the
> same way.  How can we help authors of such hooks to handle this
> change?

This part I do disagree with. Git warns (advice-optional) when I enter
detached HEAD state. When I leave my detached HEAD with some orphaned
commits within (via checkout), I get a warning unconditionally, and an
advice-optional tip to use 'git branch <name> <commit-id>'. The Jiri
hook is warning that these commits I'm making could be lost, but in fact
Git itself is telling me that (and how to save them) at the first moment
when they are actually in danger. So I think this particular example is
not a good one.

I'm more worried about ones like this, although in the rebase case it
may well be a no-op as I can't start a rebase with a dirty index:
https://github.com/alphagov/digitalmarketplace-runner/blob/f8524d9ee7465caef0571d8beb8dff4b25d10fe7/hooks/post-commit

Perhaps a good question is "what could be very catastrophic if it
happened during the middle of a rebase, but not such a bad thing if it
happened when I run 'git commit'?"

> I don't think that starting to invoke the 'post-commit' hook even more
> without going through that thought process is an acceptable fix.
> Since the "right" semantics aren't obvious here, the first step is
> probably to collect some typical examples of post-commit hooks, like
> the example from the message I'm replying to (thank you!).  Ideas for
> where we can find more?

That Github search query I posted above is a pretty big haystack. I
suppose plenty of folks who contribute to Git have a userbase and might
be familiar with some workflows in use there, right?

 - Emily

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v4 00/19] rebase: make the default backend configurable
  2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                       ` (14 preceding siblings ...)
  2019-12-24 19:54     ` [PATCH v3 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2020-01-16  6:14     ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
                         ` (20 more replies)
  15 siblings, 21 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

This is a re-roll of en/rebase-backend which has been rebased on v2.25.0 and
updated to remove the dependence on ra/rebase-i-more-options, and also tries
to address feedback from Phillip, Junio, and Jonathan. This series does a
lot of work around making the default rebase backend configurable, and
switches the default from the am backend to the merge/interactive one.

Changes since v3:

 * Rebased on origin/master and updated to remove the dependence on
   ra/rebase-i-more-options.
 * Added two new patches at the start of the series.
 * Split the old first patch into two, while modifying them based on
   Phillip's feedback (though slightly differently than discussed on the
   list; instead of making --keep-empty a synonym for --empty=keep, I
   instead kept backward compatibility by making it a no-op).
 * I noted the post-commit hook in the differences between backends. Emily
   is investigating what changes need to happen there, so I merely
   documented the existing differences.
 * dropped '-i' from the reflog messages; now they just refer to 'rebase'

Changes possibly missing from this version, for discussion:

 * I did not remove the --am option as suggested by Phillip, since Junio and
   Phillip were still discussing whether it is wanted/needed.
 * I did not address the last two items Jonathan brought up as I couldn't
   find enough information to reproduce or understand the problems.

Elijah Newren (19):
  git-rebase.txt: update description of --allow-empty-message
  t3404: directly test the behavior of interest
  rebase (interactive-backend): make --keep-empty the default
  rebase (interactive-backend): fix handling of commits that become
    empty
  t3406: simplify an already simple test
  rebase, sequencer: remove the broken GIT_QUIET handling
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase: fix handling of restrict_revision
  t3432: make these tests work with either am or merge backends
  rebase: allow more types of rebases to fast-forward
  git-rebase.txt: add more details about behavioral differences of
    backends
  rebase: move incompatibility checks between backend options a bit
    earlier
  rebase: add an --am option
  git-prompt: change the prompt for interactive-based rebases
  rebase: drop '-i' from the reflog for interactive-based rebases
  rebase tests: mark tests specific to the am-backend with --am
  rebase tests: repeat some tests using the merge backend instead of am
  rebase: make the backend configurable via config setting
  rebase: change the default backend from "am" to "merge"

 Documentation/config/rebase.txt        |   8 ++
 Documentation/git-rebase.txt           | 150 +++++++++++++++++---
 builtin/rebase.c                       | 186 +++++++++++++++++++------
 contrib/completion/git-prompt.sh       |   6 +-
 rebase-interactive.c                   |   7 +-
 rebase-interactive.h                   |   2 +-
 sequencer.c                            |  84 ++++++-----
 sequencer.h                            |   3 +-
 t/t3400-rebase.sh                      |  36 ++++-
 t/t3401-rebase-and-am-rename.sh        |   4 +-
 t/t3404-rebase-interactive.sh          |  19 +--
 t/t3406-rebase-message.sh              |  19 ++-
 t/t3407-rebase-abort.sh                |   6 +-
 t/t3420-rebase-autostash.sh            |   2 +-
 t/t3421-rebase-topology-linear.sh      |  16 +--
 t/t3424-rebase-empty.sh                | 108 ++++++++++++++
 t/t3425-rebase-topology-merges.sh      |   8 +-
 t/t3427-rebase-subtree.sh              |  12 +-
 t/t3432-rebase-fast-forward.sh         |  54 ++++---
 t/t5407-post-rewrite-hook.sh           |  12 +-
 t/t5520-pull.sh                        |  27 +++-
 t/t6047-diff3-conflict-markers.sh      |  13 +-
 t/t7512-status-help.sh                 |  12 +-
 t/t9106-git-svn-commit-diff-clobber.sh |   3 +-
 t/t9903-bash-prompt.sh                 |   8 +-
 25 files changed, 595 insertions(+), 210 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh


base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v4
Pull-Request: https://github.com/git/git/pull/679

Range-diff vs v3:

  -:  ---------- >  1:  3ea48d5394 git-rebase.txt: update description of --allow-empty-message
  -:  ---------- >  2:  10fdd162a0 t3404: directly test the behavior of interest
  1:  1c2b77e94d !  3:  179f82ab83 rebase: extend the options for handling of empty commits
     @@ -1,49 +1,46 @@
      Author: Elijah Newren <newren@gmail.com>
      
     -    rebase: extend the options for handling of empty commits
     +    rebase (interactive-backend): make --keep-empty the default
      
     -    Extend the interactive machinery with the ability to handle the full
     -    spread of options for how to handle commits that either start or become
     -    empty (by "become empty" I mean the changes in a commit are a subset of
     -    changes that exist upstream, so the net effect of applying the commit is
     -    no changes).  Introduce a new command line flag for selecting the
     -    desired behavior:
     -        --empty={drop,keep,ask}
     -    with the definitions:
     -        drop: drop empty commits
     -        keep: keep empty commits
     -        ask:  provide the user a chance to interact and pick what to do with
     -              empty commits on a case-by-case basis
     +    Different rebase backends have different treatment for commits which
     +    start empty (i.e. have no changes relative to their parent), and the
     +    --keep-empty option was added at some point to allow adjusting behavior
     +    for the interactive backend.  The handling of commits which start empty
     +    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
     +    --allow-empty-message the default, 2018-06-27), which pointed out that
     +    the behavior for various backends is often more happenstance than
     +    design.  The specific change made in that commit is actually quite
     +    relevant as well and much of the logic there directly applies here.
      
     -    Note that traditionally, am-based rebases have always dropped commits
     -    that either started or became empty, while interactive-based rebases
     -    have defaulted to ask (and provided an option to keep commits that
     -    started empty).  This difference made sense since users of an am-based
     -    rebase just wanted to quickly batch apply a sequence of commits, while
     -    users editing a todo list will likely want the chance to interact and
     -    handle unusual cases on a case-by-case basis.  However, not all rebases
     -    using the interactive machinery are explicitly interactive anymore.  In
     -    particular --merge was always meant to behave more like --am: just
     -    rebase a batch of commits without popping up a todo list.
     +    It makes a lot of sense in 'git commit' to error out on the creation of
     +    empty commits, unless an override flag is provided.  However, once
     +    someone determines that there is a rare case that merits using the
     +    manual override to create such a commit, it is somewhere between
     +    annoying and harmful to have to take extra steps to keep such
     +    intentional commits around.  Granted, empty commits are quite rare,
     +    which is why handling of them doesn't get considered much and folks tend
     +    to defer to existing (accidental) behavior and assume there was a reason
     +    for it, leading them to just add flags (--keep-empty in this case) that
     +    allow them to override the bad defaults.  Fix the interactive backend so
     +    that --keep-empty is the default, much like we did with
     +    --allow-empty-message.  The am backend should also be fixed to have
     +    --keep-empty semantics for commits that start empty, but that is not
     +    included in this patch other than a testcase documenting the failure.
      
     -    If the --empty flag is not specified, pick defaults as follows:
     -        explicitly interactive: ask
     -        --exec: keep (exec is about checking existing commits, and often
     -                      used without actually changing the base.  Thus the
     -                      expectation is that the user doesn't necessarily want
     -                      anything to change; they just want to test).
     -        otherwise: drop
     -
     -    Also, this commit makes --keep-empty just imply --empty=keep, and hides
     -    it from help so that we aren't confusing users with different ways to do
     -    the same thing.  (I could have added a --drop-empty flag, but then that
     -    invites users to specify both --keep-empty and --drop-empty and we have
     -    to add sanity checking around that; it seems cleaner to have a single
     -    multi-valued option.)  This actually fixes --keep-empty too; previously,
     -    it only meant to sometimes keep empty commits, in particular commits
     -    which started empty would be kept.  But it would still error out and ask
     -    the user what to do with commits that became empty.  Now it keeps empty
     -    commits, as instructed.
     +    Note that there was one test in t3421 which appears to have been written
     +    expecting --keep-empty to not be the default as correct behavior.  This
     +    test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
     +    empty commits", 2013-06-06), which was part of a series focusing on
     +    rebase topology and which had an interesting original cover letter at
     +    https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
     +    which noted
     +        Your input especially appreciated on whether you agree with the
     +        intent of the test cases.
     +    and then went into a long example about how one of the many tests added
     +    had several questions about whether it was correct.  As such, I believe
     +    most the tests in that series were about testing rebase topology with as
     +    many different flags as possible and were not trying to state in general
     +    how those flags should behave otherwise.
      
          Signed-off-by: Elijah Newren <newren@gmail.com>
      
     @@ -51,126 +48,62 @@
       --- a/Documentation/git-rebase.txt
       +++ b/Documentation/git-rebase.txt
      @@
     - 	original branch. The index and working tree are also left
       	unchanged as a result.
       
     -+--empty={drop,keep,ask}::
     -+	How to handle commits that become empty (because they contain a
     -+	subset of already upstream changes) or start empty.  With drop
     -+	(the default), commits that start or become empty are dropped.
     -+	With keep (implied by --exec), such commits are kept.  With ask
     -+	(implied by --interactive), the rebase will halt when an empty
     -+	commit is applied allowing you to choose whether to drop it or
     -+	commit it.  Also with ask, if the rebase is interactive then
     -+	commits which start empty will be commented out in the todo
     -+	action list (giving you a chance to uncomment).
     -++
     -+Note that this has no effect on commits which are already upstream (as
     -+can be checked via `git log --cherry-mark ...`), which are always
     -+dropped by rebase.
     -++
     -+See also INCOMPATIBLE OPTIONS below.
     -+
       --keep-empty::
      -	Keep the commits that do not change anything from its
      -	parents in the result.
     -+	Deprecated alias for what is now known as --empty=keep.
     ++	No-op.  Rebasing commits that started empty (had no change
     ++	relative to their parent) used to fail and this option would
     ++	override that behavior, allowing commits with empty changes to
     ++	be rebased.  Now commits with no changes do not cause rebasing
     ++	to halt.
       +
     - See also INCOMPATIBLE OPTIONS below.
     - 
     -@@
     -  * --interactive
     -  * --exec
     -  * --keep-empty
     -+ * --empty=
     -  * --edit-todo
     -  * --root when used in combination with --onto
     - 
     -@@
     -  * --preserve-merges and --ignore-whitespace
     -  * --preserve-merges and --committer-date-is-author-date
     -  * --preserve-merges and --ignore-date
     -+ * --preserve-merges and --empty=
     -  * --keep-base and --onto
     -  * --keep-base and --root
     +-See also INCOMPATIBLE OPTIONS below.
     ++See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
       
     + --allow-empty-message::
     + 	No-op.  Rebasing commits with an empty message used to fail
      @@
     + Empty commits
     + ~~~~~~~~~~~~~
       
     - There are some subtle differences how the backends behave.
     - 
     --Empty commits
     --~~~~~~~~~~~~~
     --
      -The am backend drops any "empty" commits, regardless of whether the
      -commit started empty (had no changes relative to its parent to
      -start with) or ended empty (all changes were already applied
      -upstream in other commits).
     --
     ++The am backend unfortunately drops intentionally empty commits, i.e.
     ++commits that started empty, though these are rare in practice.  It
     ++also drops commits that become empty and has no option for controlling
     ++this behavior.
     + 
      -The interactive backend drops commits by default that
      -started empty and halts if it hits a commit that ended up empty.
      -The `--keep-empty` option exists for the interactive backend to allow
      -it to keep commits that started empty.
     --
     ++The interactive backend keeps intentionally empty commits.
     ++Unfortunately, it always halts whenever it runs across a commit that
     ++becomes empty, even when the rebase is not explicitly interactive.
     + 
       Directory rename detection
       ~~~~~~~~~~~~~~~~~~~~~~~~~~
     - 
      
       diff --git a/builtin/rebase.c b/builtin/rebase.c
       --- a/builtin/rebase.c
       +++ b/builtin/rebase.c
     -@@
     - 	REBASE_PRESERVE_MERGES
     - };
     - 
     -+enum empty_type {
     -+	EMPTY_UNSPECIFIED = -1,
     -+	EMPTY_DROP,
     -+	EMPTY_KEEP,
     -+	EMPTY_ASK
     -+};
     -+
     - struct rebase_options {
     - 	enum rebase_type type;
     -+	enum empty_type empty;
     - 	const char *state_dir;
     - 	struct commit *upstream;
     - 	const char *upstream_name;
      @@
       	const char *action;
       	int signoff;
       	int allow_rerere_autoupdate;
      -	int keep_empty;
       	int autosquash;
     - 	int ignore_whitespace;
       	char *gpg_sign_opt;
     -@@
     - 
     - #define REBASE_OPTIONS_INIT {			  	\
     - 		.type = REBASE_UNSPECIFIED,	  	\
     -+		.empty = EMPTY_UNSPECIFIED,	  	\
     - 		.flags = REBASE_NO_QUIET, 		\
     - 		.git_am_opts = ARGV_ARRAY_INIT,		\
     - 		.git_format_patch_opt = STRBUF_INIT	\
     -@@
     - 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
     - 	replay.allow_empty = 1;
     - 	replay.allow_empty_message = opts->allow_empty_message;
     -+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
     -+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
     -+	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
     -+					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
     - 	replay.verbose = opts->flags & REBASE_VERBOSE;
     - 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
     - 	replay.committer_date_is_author_date =
     + 	int autostash;
      @@
       
       	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
       
      -	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
     -+	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
     -+	flags |= (opts->empty == EMPTY_ASK &&
     -+		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
     -+			TODO_LIST_ASK_EMPTY : 0;
       	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
       	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
       	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
     @@ -183,10 +116,8 @@
      +{
      +	struct rebase_options *opts = opt->value;
      +
     -+	BUG_ON_OPT_NEG(unset);
      +	BUG_ON_OPT_ARG(arg);
      +
     -+	opts->empty = EMPTY_KEEP;
      +	opts->type = REBASE_INTERACTIVE;
      +	return 0;
      +}
     @@ -201,62 +132,28 @@
      -		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
      +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
      +			N_("(DEPRECATED) keep empty commits"),
     -+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
     ++			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
      +			parse_opt_keep_empty },
     - 		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
     - 			 N_("allow commits with empty messages")),
     - 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
     + 		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
     + 			   N_("allow commits with empty messages"),
     + 			   PARSE_OPT_HIDDEN),
      @@
       		opts->allow_rerere_autoupdate ?
       			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
       			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
      -	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
     -+	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
       	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
       	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
       	add_var(&script_snippet, "cmd", opts->cmd);
     -@@
     - 	return 0;
     - }
     - 
     -+static enum empty_type parse_empty_value(const char *value)
     -+{
     -+	if (!strcasecmp(value, "drop"))
     -+		return EMPTY_DROP;
     -+	else if (!strcasecmp(value, "keep"))
     -+		return EMPTY_KEEP;
     -+	else if (!strcasecmp(value, "ask"))
     -+		return EMPTY_ASK;
     -+
     -+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
     -+}
     -+
     -+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
     -+{
     -+	struct rebase_options *options = opt->value;
     -+	enum empty_type value = parse_empty_value(arg);
     -+
     -+	BUG_ON_OPT_NEG(unset);
     -+
     -+	options->empty = value;
     -+	return 0;
     -+}
     -+
     - static void NORETURN error_on_missing_default_upstream(void)
     - {
     - 	struct branch *current_branch = branch_get(NULL);
      @@
       				 "ignoring them"),
       			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
       		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
      -		OPT_BOOL('k', "keep-empty", &options.keep_empty,
      -			 N_("preserve empty commits during rebase")),
     -+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
     -+			       N_("how to handle empty commits"),
     -+			       PARSE_OPT_NONEG, parse_opt_empty),
      +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
      +			N_("(DEPRECATED) keep empty commits"),
     -+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
     ++			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
      +			parse_opt_keep_empty },
       		OPT_BOOL(0, "autosquash", &options.autosquash,
       			 N_("move commits that begin with "
     @@ -267,26 +164,10 @@
       
      -	if (options.keep_empty)
      -		imply_interactive(&options, "--keep-empty");
     -+	if (options.empty != EMPTY_UNSPECIFIED)
     -+		imply_interactive(&options, "--empty");
     - 
     +-
       	if (gpg_sign) {
       		free(options.gpg_sign_opt);
     -@@
     - 		break;
     - 	}
     - 
     -+	if (options.empty == EMPTY_UNSPECIFIED) {
     -+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
     -+			options.empty = EMPTY_ASK;
     -+		else if (exec.nr > 0)
     -+			options.empty = EMPTY_KEEP;
     -+		else
     -+			options.empty = EMPTY_DROP;
     -+	}
     - 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
     - 		die(_("--reschedule-failed-exec requires "
     - 		      "--exec or --interactive"));
     + 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
      
       diff --git a/rebase-interactive.c b/rebase-interactive.c
       --- a/rebase-interactive.c
     @@ -296,19 +177,22 @@
       }
       
      -void append_todo_help(unsigned keep_empty, int command_count,
     -+void append_todo_help(unsigned no_ask_empty, int command_count,
     ++void append_todo_help(int command_count,
       		      const char *shortrevisions, const char *shortonto,
       		      struct strbuf *buf)
       {
      @@
     + 			"the rebase will be aborted.\n\n");
       
       	strbuf_add_commented_lines(buf, msg, strlen(msg));
     - 
     +-
      -	if (!keep_empty) {
     -+	if (!no_ask_empty) {
     - 		msg = _("Note that empty commits are commented out");
     - 		strbuf_add_commented_lines(buf, msg, strlen(msg));
     - 	}
     +-		msg = _("Note that empty commits are commented out");
     +-		strbuf_add_commented_lines(buf, msg, strlen(msg));
     +-	}
     + }
     + 
     + int edit_todo_list(struct repository *r, struct todo_list *todo_list,
      
       diff --git a/rebase-interactive.h b/rebase-interactive.h
       --- a/rebase-interactive.h
     @@ -318,7 +202,7 @@
       struct todo_list;
       
      -void append_todo_help(unsigned keep_empty, int command_count,
     -+void append_todo_help(unsigned no_ask_empty, int command_count,
     ++void append_todo_help(int command_count,
       		      const char *shortrevisions, const char *shortonto,
       		      struct strbuf *buf);
       int edit_todo_list(struct repository *r, struct todo_list *todo_list,
     @@ -327,157 +211,51 @@
       --- a/sequencer.c
       +++ b/sequencer.c
      @@
     - static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
     - static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
     - static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
     -+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
     -+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
     -+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
     - 
     - static int git_sequencer_config(const char *k, const char *v, void *cb)
     + 		       struct replay_opts *opts,
     + 		       struct commit *commit)
       {
     +-	int index_unchanged, empty_commit;
     ++	int index_unchanged, originally_empty;
     + 
     + 	/*
     + 	 * Three cases:
      @@
     - 	empty_commit = is_original_commit_empty(commit);
     - 	if (empty_commit < 0)
     - 		return empty_commit;
     + 	if (opts->keep_redundant_commits)
     + 		return 1;
     + 
     +-	empty_commit = is_original_commit_empty(commit);
     +-	if (empty_commit < 0)
     +-		return empty_commit;
      -	if (!empty_commit)
     -+	if (!empty_commit || opts->ask_on_initially_empty)
     ++	originally_empty = is_original_commit_empty(commit);
     ++	if (originally_empty < 0)
     ++		return originally_empty;
     ++	if (!originally_empty)
       		return 0;
       	else
       		return 1;
     -@@
     - 	char *author = NULL;
     - 	struct commit_message msg = { NULL, NULL, NULL, NULL };
     - 	struct strbuf msgbuf = STRBUF_INIT;
     --	int res, unborn = 0, reword = 0, allow;
     -+	int res, unborn = 0, reword = 0, allow, drop_commit;
     - 
     - 	if (opts->no_commit) {
     - 		/*
     -@@
     - 		goto leave;
     - 	}
     - 
     --	allow = allow_empty(r, opts, commit);
     --	if (allow < 0) {
     --		res = allow;
     --		goto leave;
     --	} else if (allow)
     --		flags |= ALLOW_EMPTY;
     --	if (!opts->no_commit) {
     -+	drop_commit = 0;
     -+	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
     -+		drop_commit = 1;
     -+		fprintf(stderr, _("No changes -- Patch already applied."));
     -+	} else {
     -+		allow = allow_empty(r, opts, commit);
     -+		if (allow < 0) {
     -+			res = allow;
     -+			goto leave;
     -+		} else if (allow) {
     -+			flags |= ALLOW_EMPTY;
     -+		}
     -+	}
     -+	if (!opts->no_commit && !drop_commit) {
     - 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
     - 			res = do_commit(r, msg_file, author, opts, flags);
     - 		else
     -@@
     - 	else if (!strcmp(key, "options.allow-empty-message"))
     - 		opts->allow_empty_message =
     - 			git_config_bool_or_int(key, value, &error_flag);
     -+	else if (!strcmp(key, "options.drop-redundant-commits"))
     -+		opts->drop_redundant_commits =
     -+			git_config_bool_or_int(key, value, &error_flag);
     - 	else if (!strcmp(key, "options.keep-redundant-commits"))
     - 		opts->keep_redundant_commits =
     - 			git_config_bool_or_int(key, value, &error_flag);
     -+	else if (!strcmp(key, "options.ask_on_initially_empty"))
     -+		opts->ask_on_initially_empty =
     -+			git_config_bool_or_int(key, value, &error_flag);
     - 	else if (!strcmp(key, "options.signoff"))
     - 		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
     - 	else if (!strcmp(key, "options.record-origin"))
     -@@
     - 		if (file_exists(rebase_path_reschedule_failed_exec()))
     - 			opts->reschedule_failed_exec = 1;
     - 
     -+		if (file_exists(rebase_path_drop_redundant_commits()))
     -+			opts->drop_redundant_commits = 1;
     -+
     -+		if (file_exists(rebase_path_keep_redundant_commits()))
     -+			opts->keep_redundant_commits = 1;
     -+
     -+		if (file_exists(rebase_path_ask_on_initially_empty()))
     -+			opts->ask_on_initially_empty = 1;
     -+
     - 		read_strategy_opts(opts, &buf);
     - 		strbuf_release(&buf);
     - 
     -@@
     - 		write_file(rebase_path_cdate_is_adate(), "%s", "");
     - 	if (opts->ignore_date)
     - 		write_file(rebase_path_ignore_date(), "%s", "");
     -+	if (opts->drop_redundant_commits)
     -+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
     -+	if (opts->keep_redundant_commits)
     -+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
     -+	if (opts->ask_on_initially_empty)
     -+		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
     - 	if (opts->reschedule_failed_exec)
     - 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
     - 
     -@@
     - 	if (opts->allow_empty_message)
     - 		res |= git_config_set_in_file_gently(opts_file,
     - 				"options.allow-empty-message", "true");
     -+	if (opts->drop_redundant_commits)
     -+		res |= git_config_set_in_file_gently(opts_file,
     -+				"options.drop-redundant-commits", "true");
     - 	if (opts->keep_redundant_commits)
     - 		res |= git_config_set_in_file_gently(opts_file,
     - 				"options.keep-redundant-commits", "true");
     -+	if (opts->ask_on_initially_empty)
     -+		res |= git_config_set_in_file_gently(opts_file,
     -+				"options.ask_on_initially_empty", "true");
     - 	if (opts->signoff)
     - 		res |= git_config_set_in_file_gently(opts_file,
     - 					"options.signoff", "true");
      @@
       				   struct rev_info *revs, struct strbuf *out,
       				   unsigned flags)
       {
      -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
     -+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
     -+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
       	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
       	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
       	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
     -@@
     - 		is_empty = is_original_commit_empty(commit);
     - 		if (!is_empty && (commit->object.flags & PATCHSAME))
     - 			continue;
     -+		if (is_empty && drop_empty)
     -+			continue;
     - 
     - 		strbuf_reset(&oneline);
     - 		pretty_print_commit(pp, commit, &oneline);
      @@
       		if (!to_merge) {
       			/* non-merge commit: easy case */
       			strbuf_reset(&buf);
      -			if (!keep_empty && is_empty)
     -+			if (is_empty && ask_empty)
     - 				strbuf_addf(&buf, "%c ", comment_line_char);
     +-				strbuf_addf(&buf, "%c ", comment_line_char);
       			strbuf_addf(&buf, "%s %s %s", cmd_pick,
       				    oid_to_hex(&commit->object.oid),
     + 				    oneline.buf);
      @@
       	struct pretty_print_context pp = {0};
       	struct rev_info revs;
       	struct commit *commit;
      -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
     -+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
     -+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
       	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
       	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
       
     @@ -491,19 +269,16 @@
       		if (!is_empty && (commit->object.flags & PATCHSAME))
       			continue;
      -		if (!keep_empty && is_empty)
     -+		if (is_empty && drop_empty)
     -+			continue;
     -+		if (is_empty && ask_empty)
     - 			strbuf_addf(out, "%c ", comment_line_char);
     +-			strbuf_addf(out, "%c ", comment_line_char);
       		strbuf_addf(out, "%s %s ", insn,
       			    oid_to_hex(&commit->object.oid));
     + 		pretty_print_commit(&pp, commit, out);
      @@
       
       	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
       	if (flags & TODO_LIST_APPEND_TODO_HELP)
      -		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
     -+		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
     -+				 count_commands(todo_list),
     ++		append_todo_help(count_commands(todo_list),
       				 shortrevisions, shortonto, &buf);
       
       	res = write_message(buf.buf, buf.len, file, 0);
     @@ -511,16 +286,6 @@
       diff --git a/sequencer.h b/sequencer.h
       --- a/sequencer.h
       +++ b/sequencer.h
     -@@
     - 	int allow_rerere_auto;
     - 	int allow_empty;
     - 	int allow_empty_message;
     -+	int drop_redundant_commits;
     - 	int keep_redundant_commits;
     -+	int ask_on_initially_empty;
     - 	int verbose;
     - 	int quiet;
     - 	int reschedule_failed_exec;
      @@
       int sequencer_skip(struct repository *repo, struct replay_opts *opts);
       int sequencer_remove_state(struct replay_opts *opts);
     @@ -530,19 +295,34 @@
       #define TODO_LIST_SHORTEN_IDS (1U << 1)
       #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
       #define TODO_LIST_REBASE_MERGES (1U << 3)
     -@@
     -  * `--onto`, we do not want to re-generate the root commits.
     -  */
     - #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
     -+#define TODO_LIST_DROP_EMPTY (1U << 7)
     -+#define TODO_LIST_ASK_EMPTY (1U << 8)
     - 
     - 
     - int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
      
       diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
       --- a/t/t3421-rebase-topology-linear.sh
       +++ b/t/t3421-rebase-topology-linear.sh
     +@@
     + test_run_rebase () {
     + 	result=$1
     + 	shift
     +-	test_expect_$result "rebase $* drops empty commit" "
     ++	test_expect_$result "rebase $* keeps begin-empty commits" "
     + 		reset_rebase &&
     +-		git rebase $* c l &&
     +-		test_cmp_rev c HEAD~2 &&
     +-		test_linear_range 'd l' c..
     ++		git rebase $* j l &&
     ++		test_cmp_rev c HEAD~4 &&
     ++		test_linear_range 'j d k l' c..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase failure ''
     + test_run_rebase success -m
     + test_run_rebase success -i
     +-test_have_prereq !REBASE_P || test_run_rebase success -p
     ++test_have_prereq !REBASE_P || test_run_rebase failure -p
     + 
     + test_run_rebase () {
     + 	result=$1
      @@
       test_run_rebase success ''
       test_run_rebase success -m
     @@ -603,31 +383,22 @@
      +	git commit -m "Five letters ought to be enough for anybody"
      +'
      +
     -+test_expect_success 'rebase --merge --empty=drop' '
     ++test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
     ++	test_when_finished "git rebase --abort" &&
      +	git checkout -B testing localmods &&
     -+	git rebase --merge --empty=drop upstream &&
     -+
     -+	test_write_lines C B A >expect &&
     -+	git log --format=%s >actual &&
     -+	test_cmp expect actual
     -+'
     ++	# rebase (--am) should not drop commits that start empty
     ++	git rebase upstream &&
      +
     -+test_expect_success 'rebase --merge --empty=keep' '
     -+	git checkout -B testing localmods &&
     -+	git rebase --merge --empty=keep upstream &&
     -+
     -+	test_write_lines D C2 C B A >expect &&
     ++	test_write_lines D C B A >expect &&
      +	git log --format=%s >actual &&
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'rebase --merge --empty=ask' '
     ++test_expect_failure 'rebase --merge with a variety of empty commits' '
     ++	test_when_finished "git rebase --abort" &&
      +	git checkout -B testing localmods &&
     -+	test_must_fail git rebase --merge --empty=ask upstream &&
     -+
     -+	test_must_fail git rebase --skip &&
     -+	git commit --allow-empty &&
     -+	git rebase --continue &&
     ++	# rebase --merge should not halt on the commit that becomes empty
     ++	git rebase --merge upstream &&
      +
      +	test_write_lines D C B A >expect &&
      +	git log --format=%s >actual &&
     @@ -636,25 +407,17 @@
      +
      +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
      +
     -+test_expect_success 'rebase --interactive --empty=drop' '
     ++test_expect_success 'rebase --interactive with a variety of empty commits' '
      +	git checkout -B testing localmods &&
     -+	git rebase --interactive --empty=drop upstream &&
     ++	test_must_fail git rebase --interactive upstream &&
      +
     -+	test_write_lines C B A >expect &&
     -+	git log --format=%s >actual &&
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'rebase --interactive --empty=keep' '
     -+	git checkout -B testing localmods &&
     -+	git rebase --interactive --empty=keep upstream &&
     ++	git rebase --skip &&
      +
     -+	test_write_lines D C2 C B A >expect &&
     ++	test_write_lines D C B A >expect &&
      +	git log --format=%s >actual &&
      +	test_cmp expect actual
      +'
      +
     -+
      +test_done
      
       diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
     @@ -665,32 +428,28 @@
       '
       
      -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
     -+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
     ++test_expect_success 'Rebase -Xsubtree --onto commit' '
       	reset_rebase &&
       	git checkout -b rebase-onto to-rebase &&
      -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
     -+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
     ++	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
       	: first pick results in no changes &&
      -	git rebase --continue &&
     -+	test_must_fail git rebase --skip &&
     -+	: last pick was an empty commit that has no changes, but we want to keep it &&
     -+	git commit --allow-empty &&
     ++	git rebase --skip &&
       	verbose test "$(commit_message HEAD~2)" = "master4" &&
       	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
       	verbose test "$(commit_message HEAD)" = "Empty commit"
       '
       
      -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
     -+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
     ++test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
       	reset_rebase &&
       	git checkout -b rebase-merges-onto to-rebase &&
      -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
     -+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
     ++	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
       	: first pick results in no changes &&
      -	git rebase --continue &&
     -+	test_must_fail git rebase --skip &&
     -+	: last pick was an empty commit that has no changes, but we want to keep it &&
     -+	git commit --allow-empty &&
     ++	git rebase --skip &&
       	verbose test "$(commit_message HEAD~2)" = "master4" &&
       	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
       	verbose test "$(commit_message HEAD)" = "Empty commit"
  -:  ---------- >  4:  c9542a2abe rebase (interactive-backend): fix handling of commits that become empty
  2:  bd3c5ec155 =  5:  9f66229d5c t3406: simplify an already simple test
  3:  49388b79fd =  6:  8d731fa39c rebase, sequencer: remove the broken GIT_QUIET handling
  4:  478479358f !  7:  b6b6597eef rebase: make sure to pass along the quiet flag to the sequencer
     @@ -8,13 +8,13 @@
       --- a/builtin/rebase.c
       +++ b/builtin/rebase.c
      @@
     + 	replay.allow_empty_message = opts->allow_empty_message;
     + 	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
       	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
     - 	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
     - 					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
      +	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
       	replay.verbose = opts->flags & REBASE_VERBOSE;
       	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
     - 	replay.committer_date_is_author_date =
     + 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
      @@
       			 N_("allow pre-rebase hook to run")),
       		OPT_NEGBIT('q', "quiet", &options.flags,
  5:  ee26f5a161 =  8:  0acefa988b rebase: fix handling of restrict_revision
  6:  34a69def33 =  9:  8c5b5b5133 t3432: make these tests work with either am or merge backends
  7:  f2c92853b4 ! 10:  b8c087d6fb rebase: allow more types of rebases to fast-forward
     @@ -35,8 +35,8 @@
       		OPT_STRING(0, "onto", &options.onto_name,
       			   N_("revision"),
      @@
     - 	    options.ignore_date)
     - 		options.flags |= REBASE_FORCE;
     + 		    state_dir_base, cmd_live_rebase, buf.buf);
     + 	}
       
      +	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
      +	    (action != ACTION_NONE) ||
     @@ -47,7 +47,9 @@
      +
       	for (i = 0; i < options.git_am_opts.argc; i++) {
       		const char *option = options.git_am_opts.argv[i], *p;
     - 		if (!strcmp(option, "--whitespace=fix") ||
     + 		if (!strcmp(option, "--committer-date-is-author-date") ||
     + 		    !strcmp(option, "--ignore-date") ||
     + 		    !strcmp(option, "--whitespace=fix") ||
       		    !strcmp(option, "--whitespace=strip"))
      -			options.flags |= REBASE_FORCE;
      +			allow_preemptive_ff = 0;
  8:  b307340f7c ! 11:  b50a1741e0 git-rebase.txt: add more details about behavioral differences of backends
     @@ -8,23 +8,24 @@
       --- a/Documentation/git-rebase.txt
       +++ b/Documentation/git-rebase.txt
      @@
     - with `--keep-base` in order to drop those commits from your branch.
       
       --ignore-whitespace::
     --	Behaves differently depending on which backend is selected.
     --+
     --'am' backend: When applying a patch, ignore changes in whitespace in
     --context lines if necessary.
     --+
     --'interactive' backend: Treat lines with only whitespace changes as
     --unchanged for the sake of a three-way merge.
     -+	Ignore whitespace-only changes in the commits being rebased,
     -+	which may avoid "unnecessary" conflicts.  (Both backends
     -+	currently have differing edgecase bugs with this option; see
     -+	BEHAVIORAL DIFFERENCES.)
     - 
       --whitespace=<option>::
     - 	This flag is passed to the 'git apply' program
     +-	These flag are passed to the 'git apply' program
     ++	These flags are passed to the 'git apply' program
     + 	(see linkgit:git-apply[1]) that applies the patch.
     + +
     + See also INCOMPATIBLE OPTIONS below.
     +@@
     + 
     +  * --committer-date-is-author-date
     +  * --ignore-date
     +- * --whitespace
     +  * --ignore-whitespace
     ++ * --whitespace
     +  * -C
     + 
     + are incompatible with the following options:
      @@
       Directory rename detection
       ~~~~~~~~~~~~~~~~~~~~~~~~~~
     @@ -82,24 +83,14 @@
      +The interactive backend works with the full commits on both sides of
      +history and thus has no such limitations.
      +
     -+--ignore-whitespace
     -+~~~~~~~~~~~~~~~~~~~
     ++Hooks
     ++~~~~~
      +
     -+The --ignore-whitespace option is supposed to ignore whitespace-only
     -+changes if it allows the code to merge cleanly.  Unfortunately, the
     -+different backends implement this differently, and both have different
     -+edge case bugs.
     -++
     -+'am' backend: When applying a patch, ignore changes in whitespace in
     -+context lines if necessary.  (Which implies that if the whitespace
     -+change was not in the context lines but on a line with a real change,
     -+then the rebase will still fail with "unnecessary" content conflicts.)
     -++
     -+'interactive' backend: Treat lines with only whitespace changes as
     -+unchanged for the sake of a three-way merge.  This means that if one
     -+side made no changes and the commits being rebased had whitespace-only
     -+changes, those whitespaces fixups will be discarded despite the fact
     -+that they present no content conflict.
     ++The am backend has not traditionally called the post-commit hook,
     ++while the merge/interactive backend has.  However, this was by
     ++accident of implementation rather than by design.  Both backends
     ++should have the same behavior, though it is not clear which one is
     ++correct.
      +
      +Miscellaneous differences
      +~~~~~~~~~~~~~~~~~~~~~~~~~
     @@ -123,19 +114,3 @@
       
       include::merge-strategies.txt[]
       
     -
     - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
     - --- a/t/t3433-rebase-options-compatibility.sh
     - +++ b/t/t3433-rebase-options-compatibility.sh
     -@@
     - GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
     - export GIT_AUTHOR_DATE
     - 
     --# This is a special case in which both am and interactive backends
     --# provide the same output. It was done intentionally because
     --# both the backends fall short of optimal behaviour.
     -+# This is a common case in which both am and interactive backends
     -+# provide the same output with --ignore-whitespace.
     - test_expect_success 'setup' '
     - 	git checkout -b topic &&
     - 	q_to_tab >file <<-\EOF &&
  9:  7c3f2e07f3 = 12:  58e6e4ffb3 rebase: move incompatibility checks between backend options a bit earlier
 10:  1df11f0b51 ! 13:  5478c730ac rebase: add an --am option
     @@ -24,8 +24,8 @@
      +See also INCOMPATIBLE OPTIONS below.
      +
       --empty={drop,keep,ask}::
     - 	How to handle commits that become empty (because they contain a
     - 	subset of already upstream changes) or start empty.  With drop
     + 	How to handle commits that are not empty to start and are not
     + 	clean cherry-picks of any upstream commit, but which become
      @@
       	Ensure at least <n> lines of surrounding context match before
       	and after each change.  When fewer lines of surrounding
     @@ -37,7 +37,7 @@
       
      @@
       --whitespace=<option>::
     - 	This flag is passed to the 'git apply' program
     + 	These flags are passed to the 'git apply' program
       	(see linkgit:git-apply[1]) that applies the patch.
      +	Implies --am.
       +
     @@ -48,9 +48,9 @@
       The following options:
       
      + * --am
     -  * --whitespace
     -  * -C
     - 
     +  * --committer-date-is-author-date
     +  * --ignore-date
     +  * --ignore-whitespace
      
       diff --git a/builtin/rebase.c b/builtin/rebase.c
       --- a/builtin/rebase.c
 11:  ff43593211 = 14:  db5e29bd81 git-prompt: change the prompt for interactive-based rebases
  -:  ---------- > 15:  413e190ac9 rebase: drop '-i' from the reflog for interactive-based rebases
 12:  99388f24e5 ! 16:  170be283a8 rebase tests: mark tests specific to the am-backend with --am
     @@ -228,40 +228,6 @@
       	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
       }
      
     - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
     - --- a/t/t3433-rebase-options-compatibility.sh
     - +++ b/t/t3433-rebase-options-compatibility.sh
     -@@
     - 	new line 2
     - 	line 3
     - 	EOF
     --	test_must_fail git rebase main side &&
     -+	test_must_fail git rebase --am main side &&
     - 	git rebase --abort &&
     --	git rebase --ignore-whitespace main side &&
     -+	git rebase --am --ignore-whitespace main side &&
     - 	test_cmp expect file
     - '
     - 
     -@@
     - 
     - test_expect_success '--committer-date-is-author-date works with am backend' '
     - 	git commit --amend &&
     --	git rebase --committer-date-is-author-date HEAD^ &&
     -+	git rebase --am --committer-date-is-author-date HEAD^ &&
     - 	git show HEAD --pretty="format:%ai" >authortime &&
     - 	git show HEAD --pretty="format:%ci" >committertime &&
     - 	test_cmp authortime committertime
     -@@
     - # sets to +0530.
     - test_expect_success '--ignore-date works with am backend' '
     - 	git commit --amend --date="$GIT_AUTHOR_DATE" &&
     --	git rebase --ignore-date HEAD^ &&
     -+	git rebase --am --ignore-date HEAD^ &&
     - 	git show HEAD --pretty="format:%ai" >authortime &&
     - 	grep "+0000" authortime
     - '
     -
       diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
       --- a/t/t5407-post-rewrite-hook.sh
       +++ b/t/t5407-post-rewrite-hook.sh
 13:  c2ba6317bf = 17:  1e3d4066c4 rebase tests: repeat some tests using the merge backend instead of am
 14:  8bec6df51a = 18:  9b4ac83d2d rebase: make the backend configurable via config setting
 15:  044853fd61 = 19:  859a4a94d7 rebase: change the default backend from "am" to "merge"

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-02-09 15:59         ` Phillip Wood
  2020-01-16  6:14       ` [PATCH v4 02/19] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
                         ` (19 subsequent siblings)
  20 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the
default", 2018-06-27) made --allow-empty-message the default and thus
turned --allow-empty-message into a no-op but did not update the
documentation to reflect this.  Update the documentation now, and hide
the option from the normal -h output since it is not useful.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt |  7 ++++---
 builtin/rebase.c             | 12 +++++++-----
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 0c4f038dd6..c83be7ffc2 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.
 See also INCOMPATIBLE OPTIONS below.
 
 --allow-empty-message::
-	By default, rebasing commits with an empty message will fail.
-	This option overrides that behavior, allowing commits with empty
-	messages to be rebased.
+	No-op.  Rebasing commits with an empty message used to fail
+	and this option would override that behavior, allowing commits
+	with empty messages to be rebased.  Now commits with an empty
+	message do not cause rebasing to halt.
 +
 See also INCOMPATIBLE OPTIONS below.
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 8081741f8a..faa4e0d406 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
 		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
-		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
-			 N_("allow commits with empty messages")),
+		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
+			   N_("allow commits with empty messages"),
+			   PARSE_OPT_HIDDEN),
 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
 		OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
 			 N_("keep original branch points of cousins")),
@@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
 				N_("add exec lines after each commit of the "
 				   "editable list")),
-		OPT_BOOL(0, "allow-empty-message",
-			 &options.allow_empty_message,
-			 N_("allow rebasing commits with empty messages")),
+		OPT_BOOL_F(0, "allow-empty-message",
+			   &options.allow_empty_message,
+			   N_("allow rebasing commits with empty messages"),
+			   PARSE_OPT_HIDDEN),
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 02/19] t3404: directly test the behavior of interest
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
                         ` (18 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3404.3 is a simple test added by commit d078c3910689 ("t3404: todo list
with commented-out commands only aborts", 2018-08-10) which was designed
to test a todo list that only contained commented-out commands.  There
were two problems with this test: (1) its title did not reflect the
purpose of the test, and (2) it tested the desired behavior through a
side-effect of other functionality instead of directly testing the
desired behavior discussed in the commit message.

Modify the test to directly test the desired behavior and update the
test title.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3404-rebase-interactive.sh | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ae6e55ce79..c41531f349 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -72,15 +72,16 @@ test_expect_success 'rebase --keep-empty' '
 	test_line_count = 6 actual
 '
 
-test_expect_success 'rebase -i with empty HEAD' '
+test_expect_success 'rebase -i with empty todo list' '
 	cat >expect <<-\EOF &&
 	error: nothing to do
 	EOF
 	(
 		set_fake_editor &&
-		test_must_fail env FAKE_LINES="1 exec_true" \
-			git rebase -i HEAD^ >actual 2>&1
+		test_must_fail env FAKE_LINES="#" \
+			git rebase -i HEAD^ >output 2>&1
 	) &&
+	tail -n 1 output >actual &&  # Ignore output about changing todo list
 	test_i18ncmp expect actual
 '
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 02/19] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-02-09 15:59         ` Phillip Wood
  2020-01-16  6:14       ` [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
                         ` (17 subsequent siblings)
  20 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior
for the interactive backend.  The handling of commits which start empty
is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
--allow-empty-message the default, 2018-06-27), which pointed out that
the behavior for various backends is often more happenstance than
design.  The specific change made in that commit is actually quite
relevant as well and much of the logic there directly applies here.

It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided.  However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around.  Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults.  Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message.  The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.

Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior.  This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
    Your input especially appreciated on whether you agree with the
    intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct.  As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt      | 24 ++++++-----
 builtin/rebase.c                  | 28 ++++++++----
 rebase-interactive.c              |  7 +--
 rebase-interactive.h              |  2 +-
 sequencer.c                       | 20 +++------
 sequencer.h                       |  2 +-
 t/t3421-rebase-topology-linear.sh | 16 +++----
 t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++
 t/t3427-rebase-subtree.sh         | 12 +++---
 9 files changed, 128 insertions(+), 55 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c83be7ffc2..1d19542d79 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.
 	unchanged as a result.
 
 --keep-empty::
-	Keep the commits that do not change anything from its
-	parents in the result.
+	No-op.  Rebasing commits that started empty (had no change
+	relative to their parent) used to fail and this option would
+	override that behavior, allowing commits with empty changes to
+	be rebased.  Now commits with no changes do not cause rebasing
+	to halt.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
 
 --allow-empty-message::
 	No-op.  Rebasing commits with an empty message used to fail
@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.
 Empty commits
 ~~~~~~~~~~~~~
 
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
+The am backend unfortunately drops intentionally empty commits, i.e.
+commits that started empty, though these are rare in practice.  It
+also drops commits that become empty and has no option for controlling
+this behavior.
 
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
+The interactive backend keeps intentionally empty commits.
+Unfortunately, it always halts whenever it runs across a commit that
+becomes empty, even when the rebase is not explicitly interactive.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/rebase.c b/builtin/rebase.c
index faa4e0d406..537b3241ce 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -77,7 +77,6 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
-	int keep_empty;
 	int autosquash;
 	char *gpg_sign_opt;
 	int autostash;
@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
 
 	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
-	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,
 	return ret;
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+				int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_INTERACTIVE;
+	return 0;
+}
+
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
-		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
 			   N_("allow commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		opts->allow_rerere_autoupdate ?
 			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
 			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
 	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_BOOL('k', "keep-empty", &options.keep_empty,
-			 N_("preserve empty commits during rebase")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
-	if (options.keep_empty)
-		imply_interactive(&options, "--keep-empty");
-
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
diff --git a/rebase-interactive.c b/rebase-interactive.c
index aa18ae82b7..7e7b4061bf 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
 	return MISSING_COMMIT_CHECK_IGNORE;
 }
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf)
 {
@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,
 			"the rebase will be aborted.\n\n");
 
 	strbuf_add_commented_lines(buf, msg, strlen(msg));
-
-	if (!keep_empty) {
-		msg = _("Note that empty commits are commented out");
-		strbuf_add_commented_lines(buf, msg, strlen(msg));
-	}
 }
 
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/rebase-interactive.h b/rebase-interactive.h
index 44dbb06311..05354ca341 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -5,7 +5,7 @@ struct strbuf;
 struct repository;
 struct todo_list;
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf);
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/sequencer.c b/sequencer.c
index b9dbf1adb0..c21fc202b1 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,
 		       struct replay_opts *opts,
 		       struct commit *commit)
 {
-	int index_unchanged, empty_commit;
+	int index_unchanged, originally_empty;
 
 	/*
 	 * Three cases:
@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,
 	if (opts->keep_redundant_commits)
 		return 1;
 
-	empty_commit = is_original_commit_empty(commit);
-	if (empty_commit < 0)
-		return empty_commit;
-	if (!empty_commit)
+	originally_empty = is_original_commit_empty(commit);
+	if (originally_empty < 0)
+		return originally_empty;
+	if (!originally_empty)
 		return 0;
 	else
 		return 1;
@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 				   struct rev_info *revs, struct strbuf *out,
 				   unsigned flags)
 {
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
 	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		if (!to_merge) {
 			/* non-merge commit: easy case */
 			strbuf_reset(&buf);
-			if (!keep_empty && is_empty)
-				strbuf_addf(&buf, "%c ", comment_line_char);
 			strbuf_addf(&buf, "%s %s %s", cmd_pick,
 				    oid_to_hex(&commit->object.oid),
 				    oneline.buf);
@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	struct pretty_print_context pp = {0};
 	struct rev_info revs;
 	struct commit *commit;
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 		return make_script_with_merges(&pp, &revs, out, flags);
 
 	while ((commit = get_revision(&revs))) {
-		int is_empty  = is_original_commit_empty(commit);
+		int is_empty = is_original_commit_empty(commit);
 
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
-		if (!keep_empty && is_empty)
-			strbuf_addf(out, "%c ", comment_line_char);
 		strbuf_addf(out, "%s %s ", insn,
 			    oid_to_hex(&commit->object.oid));
 		pretty_print_commit(&pp, commit, out);
@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
 
 	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
 	if (flags & TODO_LIST_APPEND_TODO_HELP)
-		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+		append_todo_help(count_commands(todo_list),
 				 shortrevisions, shortonto, &buf);
 
 	res = write_message(buf.buf, buf.len, file, 0);
diff --git a/sequencer.h b/sequencer.h
index 9f9ae291e3..c165e0ff25 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-#define TODO_LIST_KEEP_EMPTY (1U << 0)
+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 325072b0a3..57334dca7e 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '
 test_run_rebase () {
 	result=$1
 	shift
-	test_expect_$result "rebase $* drops empty commit" "
+	test_expect_$result "rebase $* keeps begin-empty commits" "
 		reset_rebase &&
-		git rebase $* c l &&
-		test_cmp_rev c HEAD~2 &&
-		test_linear_range 'd l' c..
+		git rebase $* j l &&
+		test_cmp_rev c HEAD~4 &&
+		test_linear_range 'j d k l' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase failure ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
+test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
 	result=$1
@@ -230,7 +230,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
new file mode 100755
index 0000000000..22d97e143b
--- /dev/null
+++ b/t/t3424-rebase-empty.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
+	test_when_finished "git rebase --abort" &&
+	git checkout -B testing localmods &&
+	# rebase (--am) should not drop commits that start empty
+	git rebase upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_failure 'rebase --merge with a variety of empty commits' '
+	test_when_finished "git rebase --abort" &&
+	git checkout -B testing localmods &&
+	# rebase --merge should not halt on the commit that becomes empty
+	git rebase --merge upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
+
+test_expect_success 'rebase --interactive with a variety of empty commits' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --interactive upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index bec48e6a1f..8dceef61cf 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
+test_expect_success 'Rebase -Xsubtree --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (2 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-02-10 14:27         ` Phillip Wood
  2020-01-16  6:14       ` [PATCH v4 05/19] t3406: simplify an already simple test Elijah Newren via GitGitGadget
                         ` (16 subsequent siblings)
  20 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design.  This commit addresses
another such corner case: commits which "become empty".

A careful reader may note that there are two types of commits which would
become empty due to a rebase:

  * [clean cherry-pick] Commits which are clean cherry-picks of upstream
    commits, as determined by `git log --cherry-mark ...`.  Re-applying
    these commits would result in an empty set of changes and a
    duplicative commit message; i.e. these are commits that have
    "already been applied" upstream.

  * [become empty] Commits which are not empty to start, are not clean
    cherry-picks of upstream commits, but which still become empty after
    being rebased.  This happens e.g. when a commit has changes which
    are a strict subset of the changes in an upstream commit, or when
    the changes of a commit can be found spread across or among several
    upstream commits.

Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.

When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes.  Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical).  As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around.  All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.

For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case.  Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes.  Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way.  As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.

Junio commented on the "become-empty" types of commits as follows[1]:

    WRT a change that ends up being empty (as opposed to a change that
    is empty from the beginning), I'd think that the current behaviour
    is desireable one.  "am" based rebase is solely to transplant an
    existing history and want to stop much less than "interactive" one
    whose purpose is to polish a series before making it publishable,
    and asking for confirmation ("this has become empty--do you want to
    drop it?") is more appropriate from the workflow point of view.

[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/

I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases.  Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.

Introduce a new command line flag for selecting the desired behavior:
    --empty={drop,keep,ask}
with the definitions:
    drop: drop commits which become empty
    keep: keep commits which become empty
    ask:  provide the user a chance to interact and pick what to do with
          commits which become empty on a case-by-case basis

In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
    explicitly interactive: ask
    otherwise: drop

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 27 ++++++++++++++++---
 builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++
 sequencer.c                  | 48 +++++++++++++++++++++++++--------
 sequencer.h                  |  1 +
 t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----
 t/t3427-rebase-subtree.sh    |  8 +++---
 6 files changed, 161 insertions(+), 25 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d19542d79..551a91d764 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--empty={drop,keep,ask}::
+	How to handle commits that are not empty to start and are not
+	clean cherry-picks of any upstream commit, but which become
+	empty after rebasing (because they contain a subset of already
+	upstream changes).  With drop (the default), commits that
+	become empty are dropped.  With keep, such commits are kept.
+	With ask (implied by --interactive), the rebase will halt when
+	an empty commit is applied allowing you to choose whether to
+	drop it, edit files more, or just commit the empty changes.
++
+Note that commits which start empty are kept, and commits which are
+clean cherry-picks (as determined by `git log --cherry-mark ...`) are
+always dropped.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --keep-empty::
 	No-op.  Rebasing commits that started empty (had no change
 	relative to their parent) used to fail and this option would
@@ -561,6 +577,7 @@ are incompatible with the following options:
  * --interactive
  * --exec
  * --keep-empty
+ * --empty=
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --interactive
  * --preserve-merges and --signoff
  * --preserve-merges and --rebase-merges
+ * --preserve-merges and --empty=
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It
 also drops commits that become empty and has no option for controlling
 this behavior.
 
-The interactive backend keeps intentionally empty commits.
-Unfortunately, it always halts whenever it runs across a commit that
-becomes empty, even when the rebase is not explicitly interactive.
+The interactive backend keeps intentionally empty commits.  Similar to
+the am backend, by default the interactive backend drops commits that
+become empty unless -i/--interactive is specified (in which case it
+stops and asks the user what to do).  The interactive backend also has
+an --empty={drop,keep,ask} option for changing the behavior of
+handling commits that become empty.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 537b3241ce..c299869e7b 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -50,8 +50,16 @@ enum rebase_type {
 	REBASE_PRESERVE_MERGES
 };
 
+enum empty_type {
+	EMPTY_UNSPECIFIED = -1,
+	EMPTY_DROP,
+	EMPTY_KEEP,
+	EMPTY_ASK
+};
+
 struct rebase_options {
 	enum rebase_type type;
+	enum empty_type empty;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -91,6 +99,7 @@ struct rebase_options {
 
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
+		.empty = EMPTY_UNSPECIFIED,	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
 	replay.allow_empty = 1;
 	replay.allow_empty_message = opts->allow_empty_message;
+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
 
 	BUG_ON_OPT_ARG(arg);
 
+	/*
+	 * If we ever want to remap --keep-empty to --empty=keep, insert:
+	 * 	opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
+	 */
 	opts->type = REBASE_INTERACTIVE;
 	return 0;
 }
@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static enum empty_type parse_empty_value(const char *value)
+{
+	if (!strcasecmp(value, "drop"))
+		return EMPTY_DROP;
+	else if (!strcasecmp(value, "keep"))
+		return EMPTY_KEEP;
+	else if (!strcasecmp(value, "ask"))
+		return EMPTY_ASK;
+
+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+	enum empty_type value = parse_empty_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+
+	options->empty = value;
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
+			       N_("how to handle empty commits"),
+			       PARSE_OPT_NONEG, parse_opt_empty),
 		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
 			N_("(DEPRECATED) keep empty commits"),
 			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
+	if (options.empty != EMPTY_UNSPECIFIED)
+		imply_interactive(&options, "--empty");
+
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
+	if (options.empty == EMPTY_UNSPECIFIED) {
+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+			options.empty = EMPTY_ASK;
+		else if (exec.nr > 0)
+			options.empty = EMPTY_KEEP;
+		else
+			options.empty = EMPTY_DROP;
+	}
 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
diff --git a/sequencer.c b/sequencer.c
index c21fc202b1..354d0b5a38 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
 static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
 
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)
 }
 
 /*
- * Do we run "git commit" with "--allow-empty"?
+ * Should empty commits be allowed?  Return status:
+ *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
+ *     0: Halt on empty commit
+ *     1: Allow empty commit
+ *     2: Drop empty commit
  */
 static int allow_empty(struct repository *r,
 		       struct replay_opts *opts,
@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,
 	int index_unchanged, originally_empty;
 
 	/*
-	 * Three cases:
+	 * Four cases:
 	 *
 	 * (1) we do not allow empty at all and error out.
 	 *
-	 * (2) we allow ones that were initially empty, but
-	 * forbid the ones that become empty;
+	 * (2) we allow ones that were initially empty, and
+	 *     just drop the ones that become empty
 	 *
-	 * (3) we allow both.
+	 * (3) we allow ones that were initially empty, but
+	 *     halt for the ones that become empty;
+	 *
+	 * (4) we allow both.
 	 */
 	if (!opts->allow_empty)
 		return 0; /* let "git commit" barf as necessary */
@@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,
 	originally_empty = is_original_commit_empty(commit);
 	if (originally_empty < 0)
 		return originally_empty;
-	if (!originally_empty)
-		return 0;
-	else
+	if (originally_empty)
 		return 1;
+	else if (opts->drop_redundant_commits)
+		return 2;
+	else
+		return 0;
 }
 
 static struct {
@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,
 	char *author = NULL;
 	struct commit_message msg = { NULL, NULL, NULL, NULL };
 	struct strbuf msgbuf = STRBUF_INIT;
-	int res, unborn = 0, reword = 0, allow;
+	int res, unborn = 0, reword = 0, allow, drop_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,
 		goto leave;
 	}
 
+	drop_commit = 0;
 	allow = allow_empty(r, opts, commit);
 	if (allow < 0) {
 		res = allow;
 		goto leave;
-	} else if (allow)
+	} else if (allow == 1) {
 		flags |= ALLOW_EMPTY;
-	if (!opts->no_commit) {
+	} else if (allow == 2) {
+		drop_commit = 1;
+		fprintf(stderr, _("No changes -- Patch already applied.\n"));
+	} // else allow == 0 and there's nothing special to do
+	if (!opts->no_commit && !drop_commit) {
 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 			res = do_commit(r, msg_file, author, opts, flags);
 		else
@@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
+		if (file_exists(rebase_path_drop_redundant_commits()))
+			opts->drop_redundant_commits = 1;
+
+		if (file_exists(rebase_path_keep_redundant_commits()))
+			opts->keep_redundant_commits = 1;
+
 		read_strategy_opts(opts, &buf);
 		strbuf_release(&buf);
 
@@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
 	if (opts->signoff)
 		write_file(rebase_path_signoff(), "--signoff\n");
+	if (opts->drop_redundant_commits)
+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
+	if (opts->keep_redundant_commits)
+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
diff --git a/sequencer.h b/sequencer.h
index c165e0ff25..3b0ab9141f 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -39,6 +39,7 @@ struct replay_opts {
 	int allow_rerere_auto;
 	int allow_empty;
 	int allow_empty_message;
+	int drop_redundant_commits;
 	int keep_redundant_commits;
 	int verbose;
 	int quiet;
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
index 22d97e143b..dcb4cb4751 100755
--- a/t/t3424-rebase-empty.sh
+++ b/t/t3424-rebase-empty.sh
@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '
 	git commit -m "Five letters ought to be enough for anybody"
 '
 
-test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
+test_expect_failure 'rebase (am-backend)' '
 	test_when_finished "git rebase --abort" &&
 	git checkout -B testing localmods &&
 	# rebase (--am) should not drop commits that start empty
@@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
 	test_cmp expect actual
 '
 
-test_expect_failure 'rebase --merge with a variety of empty commits' '
-	test_when_finished "git rebase --abort" &&
+test_expect_success 'rebase --merge --empty=drop' '
 	git checkout -B testing localmods &&
-	# rebase --merge should not halt on the commit that becomes empty
-	git rebase --merge upstream &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	git rebase --skip &&
 
 	test_write_lines D C B A >expect &&
 	git log --format=%s >actual &&
@@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '
 
 GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
 
-test_expect_success 'rebase --interactive with a variety of empty commits' '
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=ask' '
 	git checkout -B testing localmods &&
-	test_must_fail git rebase --interactive upstream &&
+	test_must_fail git rebase --interactive --empty=ask upstream &&
 
 	git rebase --skip &&
 
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index 8dceef61cf..79e43a370b 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
 	: first pick results in no changes &&
 	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
 	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 05/19] t3406: simplify an already simple test
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (3 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 06/19] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
                         ` (15 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

When the merge backend was re-implemented on top of the interactive
backend, the output of rebase --merge changed a little.  This change
allowed this test to be simplified, though it wasn't noticed until now.
Simplify the testcase a little.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3406-rebase-message.sh | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index b393e1e9fe..0c2c569f95 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -18,11 +18,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 06/19] rebase, sequencer: remove the broken GIT_QUIET handling
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (4 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 05/19] t3406: simplify an already simple test Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 07/19] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
                         ` (14 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

The GIT_QUIET environment variable was used to signal the non-am
backends that the rebase should perform quietly.  The preserve-merges
backend does not make use of the quiet flag anywhere (other than to
write out its state whenever it writes state), and this mechanism was
broken in the conversion from shell to C.  Since this environment
variable was specifically designed for scripts and the only backend that
would still use it is no longer a script, just gut this code.

A subsequent commit will fix --quiet for the interactive/merge backend
in a different way.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 6 ++----
 sequencer.c      | 6 ++----
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index c299869e7b..57875485df 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 	write_file(state_dir_path("orig-head", opts), "%s",
 		   oid_to_hex(&opts->orig_head));
-	write_file(state_dir_path("quiet", opts), "%s",
-		   opts->flags & REBASE_NO_QUIET ? "" : "t");
+	if (!(opts->flags & REBASE_NO_QUIET))
+		write_file(state_dir_path("quiet", opts), "%s", "");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
 	add_var(&script_snippet, "git_am_opt", buf.buf);
 	strbuf_release(&buf);
diff --git a/sequencer.c b/sequencer.c
index 354d0b5a38..078a68eaf3 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2568,8 +2568,6 @@ static void write_strategy_opts(struct replay_opts *opts)
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const char *orig_head)
 {
-	const char *quiet = getenv("GIT_QUIET");
-
 	if (head_name)
 		write_file(rebase_path_head_name(), "%s\n", head_name);
 	if (onto)
@@ -2578,8 +2576,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 	if (orig_head)
 		write_file(rebase_path_orig_head(), "%s\n", orig_head);
 
-	if (quiet)
-		write_file(rebase_path_quiet(), "%s\n", quiet);
+	if (opts->quiet)
+		write_file(rebase_path_quiet(), "%s", "");
 	if (opts->verbose)
 		write_file(rebase_path_verbose(), "%s", "");
 	if (opts->strategy)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 07/19] rebase: make sure to pass along the quiet flag to the sequencer
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (5 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 06/19] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 08/19] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
                         ` (13 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  | 3 ++-
 t/t3400-rebase.sh | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 57875485df..e8d518ac8d 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.allow_empty_message = opts->allow_empty_message;
 	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
 	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			 N_("allow pre-rebase hook to run")),
 		OPT_NEGBIT('q', "quiet", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+			   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
 		OPT_BIT('v', "verbose", &options.flags,
 			N_("display a diffstat of what changed upstream"),
 			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 221b35f2df..79762b989a 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
 	git rebase -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	(
 		echo "One" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 08/19] rebase: fix handling of restrict_revision
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (6 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 07/19] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 09/19] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
                         ` (12 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

restrict_revision in the original shell script was an excluded revision
range.  It is also treated that way by the am-backend.  In the
conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the
interactive-backend accidentally treated it as a positive revision
rather than a negated one.

This was missed as there were no tests in the testsuite that tested an
interactive rebase with fork-point behavior.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  |  4 ++--
 t/t3400-rebase.sh | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index e8d518ac8d..fe1175af32 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 
 	argv_array_pushl(&make_script_args, "", revisions, NULL);
 	if (opts->restrict_revision)
-		argv_array_push(&make_script_args,
-				oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_pushf(&make_script_args, "^%s",
+				 oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
 				    make_script_args.argc, make_script_args.argv,
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 79762b989a..71fd6396cd 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
 	git rebase master
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
 	git rebase &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 09/19] t3432: make these tests work with either am or merge backends
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (7 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 08/19] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 10/19] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
                         ` (11 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3432 had several stress tests for can_fast_forward(), whose intent was
to ensure we were using the optimization of just fast forwarding when
possible.  However, these tests verified that fast forwards had happened
based on the output that rebase printed to the terminal.  We can instead
test more directly that we actually fast-forwarded by checking the
reflog, which also has the side effect of making the tests applicable
for the merge/interactive backend.

This change does lose the distinction between "noop" and "noop-force",
but as stated in commit c9efc216830f ("t3432: test for --no-ff's
interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo,
just test for what we're currently doing.".

This change does not actually run these tests with the merge/interactive
backend; instead this is just a preparatory commit.  A subsequent commit
which fixes can_fast_forward() to work with that backend will then also
change t3432 to add tests of that backend as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 92f95b57da..7432c0e241 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -44,19 +44,15 @@ test_rebase_same_head_ () {
 	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
 		oldhead=\$(git rev-parse HEAD) &&
 		test_when_finished 'git reset --hard \$oldhead' &&
+		cp .git/logs/HEAD expect &&
 		git rebase$flag $* >stdout &&
 		if test $what = work
 		then
-			# Must check this case first, for 'is up to
-			# date, rebase forced[...]rewinding head' cases
-			test_i18ngrep 'rewinding head' stdout
+			old=\$(wc -l <expect) &&
+			test_line_count '-gt' \$old .git/logs/HEAD
 		elif test $what = noop
 		then
-			test_i18ngrep 'is up to date' stdout &&
-			test_i18ngrep ! 'rebase forced' stdout
-		elif test $what = noop-force
-		then
-			test_i18ngrep 'is up to date, rebase forced' stdout
+			test_cmp expect .git/logs/HEAD
 		fi &&
 		newhead=\$(git rev-parse HEAD) &&
 		if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () {
 
 changes='no changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '
 
 changes='our changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '
 '
 
 changes='our and their changes'
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
 test_rebase_same_head success noop same success work diff --onto master... master
 test_rebase_same_head success noop same success work diff --keep-base master
 test_rebase_same_head success noop same success work diff --keep-base
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 10/19] rebase: allow more types of rebases to fast-forward
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (8 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 09/19] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 11/19] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
                         ` (10 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we dis-allowed rebases using the interactive backend from
performing a fast-forward to short-circuit the rebase operation.  This
made sense for explicitly interactive rebases and some implicitly
interactive rebases, but certainly became overly stringent when the
merge backend was re-implemented via the interactive backend.

Just as the am-based rebase has always had to disable the fast-forward
based on a variety of conditions or flags (e.g. --signoff, --whitespace,
etc.), we need to do the same but now with a few more options.  However,
continuing to use REBASE_FORCE for tracking this is problematic because
the interactive backend used it for a different purpose.  (When
REBASE_FORCE wasn't set, the interactive backend would not fast-forward
the whole series but would fast-forward individual "pick" commits at the
beginning of the todo list, and then a squash or something would cause
it to start generating new commits.)  So, introduce a new
allow_preemptive_ff flag contained within cmd_rebase() and use it to
track whether we are going to allow a pre-emptive fast-forward that
short-circuits the whole rebase.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c               | 18 ++++++++++++++----
 t/t3432-rebase-fast-forward.sh |  2 ++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index fe1175af32..4b7d8fc908 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
+	int allow_preemptive_ff = 1;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		    state_dir_base, cmd_live_rebase, buf.buf);
 	}
 
+	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+	    (action != ACTION_NONE) ||
+	    (exec.nr > 0) ||
+	    options.autosquash) {
+		allow_preemptive_ff = 0;
+	}
+
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
 		if (!strcmp(option, "--committer-date-is-author-date") ||
 		    !strcmp(option, "--ignore-date") ||
 		    !strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
-			options.flags |= REBASE_FORCE;
+			allow_preemptive_ff = 0;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	/*
 	 * Check if we are already based on onto with linear history,
 	 * in which case we could fast-forward without replacing the commits
-	 * with new commits recreated by replaying their changes. This
-	 * optimization must not be done if this is an interactive rebase.
+	 * with new commits recreated by replaying their changes.
+	 *
+	 * Note that can_fast_forward() initializes merge_base, so we have to
+	 * call it before checking allow_preemptive_ff.
 	 */
 	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
 		    &options.orig_head, &merge_base) &&
-	    !is_interactive(&options)) {
+	    allow_preemptive_ff) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 7432c0e241..40388ccf9f 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -30,6 +30,8 @@ test_rebase_same_head () {
 	shift &&
 	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 11/19] git-rebase.txt: add more details about behavioral differences of backends
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (9 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 10/19] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 12/19] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
                         ` (9 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++---
 1 file changed, 80 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 551a91d764..df02d76e2d 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -414,7 +414,7 @@ with `--keep-base` in order to drop those commits from your branch.
 
 --ignore-whitespace::
 --whitespace=<option>::
-	These flag are passed to the 'git apply' program
+	These flags are passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
 +
 See also INCOMPATIBLE OPTIONS below.
@@ -561,8 +561,8 @@ The following options:
 
  * --committer-date-is-author-date
  * --ignore-date
- * --whitespace
  * --ignore-whitespace
+ * --whitespace
  * -C
 
 are incompatible with the following options:
@@ -613,9 +613,84 @@ handling commits that become empty.
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Directory rename heuristics are enabled in the merge and interactive
-backends.  Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the am backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge and interactive
+backends to provide you warnings in such cases.
+
+Context
+~~~~~~~
+
+The am backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally).  Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes.  The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file.  The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines.  However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked.  There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The interactive backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from.  Since the am backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary.  Also, when merge.conflictStyle is
+set to diff3, the am backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The interactive backend works with the full commits on both sides of
+history and thus has no such limitations.
+
+Hooks
+~~~~~
+
+The am backend has not traditionally called the post-commit hook,
+while the merge/interactive backend has.  However, this was by
+accident of implementation rather than by design.  Both backends
+should have the same behavior, though it is not clear which one is
+correct.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+  the changes made in the reflog, though both will make use of the
+  word "rebase".
+
+* Progress, informational, and error messages: The two backends
+  provide slightly different progress and informational messages.
+  Also, the am backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the interactive backend
+  writes them to stderr.
+
+* State directories: The two backends keep their state in different
+  directories under .git/
 
 include::merge-strategies.txt[]
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 12/19] rebase: move incompatibility checks between backend options a bit earlier
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (10 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 11/19] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 13/19] rebase: add an --am option Elijah Newren via GitGitGadget
                         ` (8 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 4b7d8fc908..55a0b2a288 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
+	if (options.git_am_opts.argc) {
+		/* all am options except -q are compatible only with --am */
+		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+			if (strcmp(options.git_am_opts.argv[i], "-q"))
+				break;
+
+		if (is_interactive(&options) && i >= 0)
+			die(_("cannot combine am options with either "
+			      "interactive or merge options"));
+	}
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (reschedule_failed_exec >= 0)
 		options.reschedule_failed_exec = reschedule_failed_exec;
 
-	if (options.git_am_opts.argc) {
-		/* all am options except -q are compatible only with --am */
-		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
-			if (strcmp(options.git_am_opts.argv[i], "-q"))
-				break;
-
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
-	}
-
 	if (options.signoff) {
 		if (options.type == REBASE_PRESERVE_MERGES)
 			die("cannot combine '--signoff' with "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 13/19] rebase: add an --am option
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (11 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 12/19] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 14/19] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
                         ` (7 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Currently, this option doesn't do anything except error out if any
options requiring the interactive-backend are also passed.  However,
when we make the default backend configurable later in this series, this
flag will provide a way to override the config setting.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 11 ++++++++++-
 builtin/rebase.c             | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index df02d76e2d..5230084be9 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--am:
+	Use git-am internally to rebase.  This option may become a
+	no-op in the future once the interactive backend handles
+	everything the am one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --empty={drop,keep,ask}::
 	How to handle commits that are not empty to start and are not
 	clean cherry-picks of any upstream commit, but which become
@@ -376,7 +383,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.
+	ever ignored.  Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -416,6 +423,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	These flags are passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
+	Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -559,6 +567,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
+ * --am
  * --committer-date-is-author-date
  * --ignore-date
  * --ignore-whitespace
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 55a0b2a288..6884590258 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	return res && is_linear_history(onto, head);
 }
 
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_AM;
+
+	return 0;
+}
+
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
+		{ OPTION_CALLBACK, 0, "am", &options, NULL,
+			N_("use apply-mail strategies to rebase"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
 			N_("use merging strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc) {
+	if (options.git_am_opts.argc || options.type == REBASE_AM) {
 		/* all am options except -q are compatible only with --am */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 14/19] git-prompt: change the prompt for interactive-based rebases
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (12 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 13/19] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 15/19] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
                         ` (6 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we had different prompts for different types of rebases:
   REBASE: for am-based rebases
   REBASE-m: for merge-based rebases
   REBASE-i: for interactive-based rebases

It's not clear why this distinction was necessary or helpful; when the
prompt was added in commit e75201963f67 ("Improve bash prompt to detect
various states like an unfinished merge", 2007-09-30), it simply added
these three different types.  Perhaps there was a useful purpose back
then, but there have been some changes:

  * The merge backend was deleted after being implemented on top of the
    interactive backend, causing the prompt for merge-based rebases to
    change from REBASE-m to REBASE-i.
  * The interactive backend is used for multiple different types of
    non-interactive rebases, so the "-i" part of the prompt doesn't
    really mean what it used to.
  * Rebase backends have gained more abilities and have a great deal of
    overlap, sometimes making it hard to distinguish them.
  * Behavioral differences between the backends have also been ironed
    out.
  * We want to change the default backend from am to interactive, which
    means people would get "REBASE-i" by default if we didn't change
    the prompt, and only if they specified --am or --whitespace or -C
    would they get the "REBASE" prompt.
  * In the future, we plan to have "--whitespace", "-C", and even "--am"
    run the interactive backend once it can handle everything the
    am-backend can.

For all these reasons, make the prompt for any type of rebase just be
"REBASE".

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 contrib/completion/git-prompt.sh | 6 +-----
 t/t9903-bash-prompt.sh           | 8 ++++----
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd47b..014cd7c3cf 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -429,11 +429,7 @@ __git_ps1 ()
 		__git_eread "$g/rebase-merge/head-name" b
 		__git_eread "$g/rebase-merge/msgnum" step
 		__git_eread "$g/rebase-merge/end" total
-		if [ -f "$g/rebase-merge/interactive" ]; then
-			r="|REBASE-i"
-		else
-			r="|REBASE-m"
-		fi
+		r="|REBASE"
 	else
 		if [ -d "$g/rebase-apply" ]; then
 			__git_eread "$g/rebase-apply/next" step
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 88bc733ad6..7ca35d358d 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
 '
 
 test_expect_success 'prompt - interactive rebase' '
-	printf " (b1|REBASE-i 2/3)" >expected &&
+	printf " (b1|REBASE 2/3)" >expected &&
 	write_script fake_editor.sh <<-\EOF &&
 		echo "exec echo" >"$1"
 		echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
 '
 
 test_expect_success 'prompt - rebase merge' '
-	printf " (b2|REBASE-i 1/3)" >expected &&
+	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
 	test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
+test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --am b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 15/19] rebase: drop '-i' from the reflog for interactive-based rebases
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (13 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 14/19] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 16/19] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
                         ` (5 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

A large variety of rebase types are supported by the interactive
machinery, not just the explicitly interactive ones.  These all share
the same code and write the same reflog messages, but the "-i" moniker
in those messages doesn't really have much meaning.  It also becomes
somewhat distracting once we switch the default from the am-backend to
the interactive one.  Just remove the "-i" from these messages.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c              |  2 +-
 sequencer.c                   | 12 ++++++------
 t/t3404-rebase-interactive.sh | 10 +++++-----
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6884590258..ecd23d3ee7 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)
 	if (env && strcmp("rebase", env))
 		return; /* only override it if it is "rebase" */
 
-	strbuf_addf(&buf, "rebase -i (%s)", options->action);
+	strbuf_addf(&buf, "rebase (%s)", options->action);
 	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
 	strbuf_release(&buf);
 }
diff --git a/sequencer.c b/sequencer.c
index 078a68eaf3..cca503c11b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)
 			char *eol = strchr(p, '\n');
 			if (eol)
 				*eol = '\0';
-			if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) {
+			if (delete_ref("(rebase) cleanup", p, NULL, 0) < 0) {
 				warning(_("could not delete '%s'"), p);
 				ret = -1;
 			}
@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)
 	case REPLAY_PICK:
 		return N_("cherry-pick");
 	case REPLAY_INTERACTIVE_REBASE:
-		return N_("rebase -i");
+		return N_("rebase");
 	}
 	die(_("unknown action: %d"), opts->action);
 }
@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r,
 			       COMMIT_LOCK | SKIP_IF_UNCHANGED))
 		/*
 		 * TRANSLATORS: %s will be "revert", "cherry-pick" or
-		 * "rebase -i".
+		 * "rebase".
 		 */
 		return error(_("%s: Unable to write new index file"),
 			_(action_name(opts)));
@@ -2885,8 +2885,8 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
 	int next = todo_list->current, offset, fd;
 
 	/*
-	 * rebase -i writes "git-rebase-todo" without the currently executing
-	 * command, appending it to "done" instead.
+	 * interactive backend writes "git-rebase-todo" without the currently
+	 * executing command, appending it to "done" instead.
 	 */
 	if (is_rebase_i(opts))
 		next++;
@@ -3197,7 +3197,7 @@ static int do_label(struct repository *r, const char *name, int len)
 		return error(_("illegal label name: '%.*s'"), len, name);
 
 	strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
-	strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
+	strbuf_addf(&msg, "rebase (label) '%.*s'", len, name);
 
 	transaction = ref_store_transaction_begin(refs, &err);
 	if (!transaction) {
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index c41531f349..a31583eb2f 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' '
 '
 
 test_expect_success 'reflog for the branch shows correct finish message' '
-	printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \
+	printf "rebase (finish): refs/heads/branch1 onto %s\n" \
 		"$(git rev-parse branch2)" >expected &&
 	git log -g --pretty=%gs -1 refs/heads/branch1 >actual &&
 	test_cmp expected actual
@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' '
 	git branch -f branch-reflog-test H &&
 	git rebase -i --onto I F branch-reflog-test &&
 	cat >expect <<-\EOF &&
-	rebase -i (finish): returning to refs/heads/branch-reflog-test
-	rebase -i (pick): H
-	rebase -i (pick): G
-	rebase -i (start): checkout I
+	rebase (finish): returning to refs/heads/branch-reflog-test
+	rebase (pick): H
+	rebase (pick): G
+	rebase (start): checkout I
 	EOF
 	git reflog -n4 HEAD |
 	sed "s/[^:]*: //" >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 16/19] rebase tests: mark tests specific to the am-backend with --am
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (14 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 15/19] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 17/19] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
                         ` (4 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

We have many rebase tests in the testsuite, and often the same test is
repeated multiple times just testing different backends.  For those
tests that were specifically trying to test the am backend, add the --am
flag.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3400-rebase.sh                 | 10 +++++-----
 t/t3401-rebase-and-am-rename.sh   |  4 ++--
 t/t3404-rebase-interactive.sh     |  2 +-
 t/t3406-rebase-message.sh         | 12 ++++++------
 t/t3407-rebase-abort.sh           |  6 +++---
 t/t3420-rebase-autostash.sh       |  2 +-
 t/t3425-rebase-topology-merges.sh |  8 ++++----
 t/t3432-rebase-fast-forward.sh    |  4 ++--
 t/t5407-post-rewrite-hook.sh      | 12 ++++++------
 t/t7512-status-help.sh            | 12 ++++++------
 10 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 71fd6396cd..0a491f2363 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 
 test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --am -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase -f --onto init HEAD^ &&
+		test_must_fail git rebase --am -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index a0b9438b22..50803958fd 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --am: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --am A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index a31583eb2f..f964b2cd41 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 0c2c569f95..7ce617fc1f 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --am master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --am topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase reflog-onto &&
+	git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 910f218284..3e31826170 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --am --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --am master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 5f7e73cf83..3816159e20 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index fd8efe84fe..19700b025b 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --am
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 40388ccf9f..4b3cecce56 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 7344253bfb..a8a73616e4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --am' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --am --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --am --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --am --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 66d7a62797..d22b0acf2a 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --am in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --am in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --am conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 17/19] rebase tests: repeat some tests using the merge backend instead of am
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (15 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 16/19] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 18/19] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
                         ` (3 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In order to ensure the merge/interactive backend gets similar coverage
to the am one, add some tests for cases where previously only the am
backend was tested.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t5520-pull.sh                   | 17 +++++++++++++++--
 t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 602d996a33..3fff6a06fa 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -277,14 +277,27 @@ test_expect_success '--rebase' '
 	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=am pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index 860542aad0..d383ce8130 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '
 	)
 '
 
-test_expect_success 'rebase describes fake ancestor base' '
+test_expect_success 'rebase --merge describes parent of commit being picked' '
 	test_create_repo rebase &&
 	(
 		cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '
 		test_commit master file &&
 		git checkout -b side HEAD^ &&
 		test_commit side file &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --am describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
 		grep "||||||| constructed merge base" file
 	)
 '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 18/19] rebase: make the backend configurable via config setting
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (16 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 17/19] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-16  6:14       ` [PATCH v4 19/19] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
                         ` (2 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt |  8 ++++++++
 builtin/rebase.c                | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d812..e6ae30c999 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,14 @@ rebase.useBuiltin::
 	is always used. Setting this will emit a warning, to alert any
 	remaining users that setting this now does nothing.
 
+rebase.backend::
+	Default backend to use for rebasing.  Possible choices are
+	'am' or 'merge' (note that the merge backend is sometimes also
+	refered to as the interactive backend or the interactive
+	machinery elsewhere in the docs).  Also, in the future, if the
+	merge backend gains all remaining capabilities of the am
+	backend, this setting may become unused.
+
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
 	rebase. False by default.
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ecd23d3ee7..b96de5809e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -60,6 +60,7 @@ enum empty_type {
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
+	const char *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -100,6 +101,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
+		.default_backend = "am",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.backend")) {
+		return git_config_string(&opts->default_backend, var, value);
+	}
+
 	return git_default_config(var, value, data);
 }
 
@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
+		if (i >= 0) {
+			if (is_interactive(&options))
+				die(_("cannot combine am options with either "
+				      "interactive or merge options"));
+			else
+				options.type = REBASE_AM;
+		}
+	}
+
+	if (options.type == REBASE_UNSPECIFIED) {
+		if (!strcmp(options.default_backend, "merge"))
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "am"))
+			options.type = REBASE_AM;
+		else
+			die(_("Unknown rebase backend: %s"),
+			    options.default_backend);
 	}
 
 	switch (options.type) {
@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		options.state_dir = apply_dir();
 		break;
 	default:
-		/* the default rebase backend is `--am` */
-		options.type = REBASE_AM;
-		options.state_dir = apply_dir();
-		break;
+		BUG("options.type was just set above; should be unreachable.");
 	}
 
 	if (options.empty == EMPTY_UNSPECIFIED) {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v4 19/19] rebase: change the default backend from "am" to "merge"
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (17 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 18/19] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
@ 2020-01-16  6:14       ` Elijah Newren via GitGitGadget
  2020-01-17 16:58       ` [PATCH v4 00/19] rebase: make the default backend configurable Phillip Wood
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-01-16  6:14 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

The am-backend drops information and thus limits what we can do:

  * lack of full tree information from the original commits means we
    cannot do directory rename detection and warn users that they might
    want to move some of their new files that they placed in old
    directories to prevent their becoming orphaned.[1]
  * reduction in context from only having a few lines beyond those
    changed means that when context lines are non-unique we can apply
    patches incorrectly.[2]
  * lack of access to original commits means that conflict marker
    annotation has less information available.

Also, the merge/interactive backend have far more abilities, appear to
currently have a slight performance advantage[3] and have room for more
optimizations than the am backend[4] (and work is underway to take
advantage of some of those possibilities).

[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt           |  2 +-
 builtin/rebase.c                       |  4 ++--
 t/t5520-pull.sh                        | 10 ++++++----
 t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 5230084be9..880e8bff5a 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -313,7 +313,7 @@ See also INCOMPATIBLE OPTIONS below.
 --merge::
 	Use merging strategies to rebase.  When the recursive (default) merge
 	strategy is used, this allows rebase to be aware of renames on the
-	upstream side.
+	upstream side.  This is the default.
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
diff --git a/builtin/rebase.c b/builtin/rebase.c
index b96de5809e..5c5526f42e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -101,7 +101,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "am",	  	\
+		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			options.type = REBASE_MERGE;
+			imply_interactive(&options, "--merge");
 		else if (!strcmp(options.default_backend, "am"))
 			options.type = REBASE_AM;
 		else
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3fff6a06fa..4f9e7f7ff6 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
 	(
 		cd dst &&
 		test_must_fail git pull --rebase &&
-		find .git/rebase-apply -name "000*" >patches &&
-		test_line_count = 1 patches
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d..aec45bca3b 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-12 17:59             ` Johannes Schindelin
@ 2020-01-16  6:32               ` Elijah Newren
  2020-01-16  7:58                 ` Jonathan Nieder
  2020-01-16 10:48                 ` Johannes Schindelin
  0 siblings, 2 replies; 161+ messages in thread
From: Elijah Newren @ 2020-01-16  6:32 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Phillip Wood, Jonathan Nieder, Elijah Newren via GitGitGadget,
	Git Mailing List, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi,

On Sun, Jan 12, 2020 at 9:59 AM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>
> Hi,
>
> On Sat, 11 Jan 2020, Phillip Wood wrote:
>
> > On 11/01/2020 01:16, Elijah Newren wrote:
> > >
> > > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
> > > >
> > > > Elijah Newren via GitGitGadget wrote:
> > > >
> > > >   1. "git rebase --am" does not invoke the post-commit hook, but "git
> > > >      rebase --merge" does.  Is this behavior change intended?
> > > >
> > > >      Noticed because jiri[1] installs a post-commit hook that warns
> > > >      about commits on detached HEAD, so this change makes rebases more
> > > >      noisy in repositories that were set up using jiri.
> >
> > Perhaps that hook could learn not to warn if a branch is being rebased?
> > git could be more helpful there by having a porcelain option to status
> > that prints the branch name if we're rebasing (`git worktree --list`
> > shows the branch correctly when it's being rebased but does not (yet - I
> > have a patch to do it) mark the current worktree so isn't very helpful.)
> >
> > > I've never used a post-commit hook or seen one in the wild.  Certainly
> > > wasn't intentional, but it's not clear to me if it's wrong or right
> > > either.  I don't see why it would make sense to distinguish between
> > > any of git rebase --am/--merge/--interactive, but it isn't too
> > > surprising that by historical accident the two rebase backends which
> > > happened to call git-commit behind the scenes would call a post-commit
> > > hook and the other rebase backend that didn't call git-commit
> > > wouldn't.
> >
> > Looking through the history the am based rebase has never run the post-commit
> > hook as am has its own set of hooks and the scripted version used commit-tree.
> > The merge based rebase ran `git commit` which ran the post commit hook. The
> > interactive rebase ran the hook until and I broke it in a356ee4659b
> > ("sequencer: try to commit without forking 'git commit'", 2017-11-24) and
> > after I fixed it in 4627bc777e ("sequencer: run post-commit hook",
> > 2019-10-15). As it was broken for two years with no one noticing it can't be
> > that popular.
>
> Maybe a crazy idea, but maybe not: how about running the `post-commit`
> hook _only_ if `--merge` was specified explicitly, and in that case (and
> guarded behind a check verifying that the `post-commit` hook _actually_
> exists _and_ is executable) warn the user that this hook won't be run in
> future versions?
>
> To make things better for users who actually want to run that hook during
> rebases, we could introduce a config option, say,
> `rebase.runPostCommitHook` that is a tri-state (`true`, `false`,
> `onlyForDashDashMerge`, at first defaulting to the last, eventually to
> `false`).
>
> Crazy? Or helpful?

Seems crazy.  Why would you want it just for --merge?  If anything, I
would think --merge should be most like --am; if some mode of rebase
were to be considered special, I'd think it'd only be the _explicitly_
interactive case.  But I don't see the justification for treating any
of the rebase modes differently.  I think the hook should be on for
all of them, or off for all of them, and I could go either way.

(Honestly, it's tempting to just fix the fact that the interactive
backend needlessly forks a "git commit" process by having it commit on
its own much like builtin/merge.c does.  Then omit calling the
post-commit hook and it behaves the same as the am backend and no one
in the world notices because no one in the world uses or cares about
that hook except a few people at Google who happen to be used to the
am-backend and even then aren't convinced whether invoking the hook or
not is right.  And if that's wrong and someone has a solid argument
about why and the harm that not calling it does, then big deal, we add
calling the hook sometime in the future...)

> > > But the big question here, is what is correct behavior?  Should rebase
> > > call the post-commit hook, or should it skip it?  I haven't any clue
> > > what the answer to that is.
> >
> > It's creating a new commit so I lean towards thinking it should run the
> > post-commit hook. As an example I have a post-commit hook that prints a
> > warning if a commit is created on a branch that is being rewritten by
> > one of my scripts in another worktree. There are pre-commit and
> > pre-rebase hooks to try and prevent that, but the warning is there as a
> > last resort if those hooks are by-passed.
>
> I guess you're right, it is quite surprising that the `post-commit` hook
> is _not_ run for `--am` rebases even though commits are created.

I guess part of the problem is whether people think of it as "new
commits" or just any commits.  "New commits" are created by "git
commit" and "git merge".  rebase and cherry-pick just create
derivatives of existing commits.  Given the existence of the
post-rewrite hook, one could argue that the distinction has merit.
I'm not sure either way, so I'm glad Emily started investigating.  I'd
rather not deal with that can of worms...  :-)

> > > >   2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is
> > > >      not interactive.
> >
> > If this is important to people I think it should be easy enough to set
> > GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long
> > as it hasn't already been set by the user) rather than relying on
> > sequencer.c to do it.
>
> I agree (but won't have time to implement it, so maybe I should shut up
> already...)
>
> > > Yep, as does --keep, --exec, --rebase-merges, etc.  There are lots of
> > > rebases which use the interactive machinery even if they aren't
> > > explicitly interactive.  I've never seen the "-i" in the reflog
> > > message defined, but clearly it has always been used whenever the
> > > interactive machinery was in play regardless of whether the rebase was
> > > interactive.  In that regard, I figured that --merge fit in rather
> > > nicely.  (And I noted the fact that reflog messages were different
> > > between the backends among the "BEHAVIORAL DIFFERENCES" section of
> > > git-rebase.txt).  But if others think we should just drop the -i (much
> > > as we did for the bash prompt), I'd be happy with that too.  If we go
> > > that route, I think I'd rather drop the -i in the reflog for all
> > > rebases, not just the
> > > using-the-interactive-machinery-but-not-explicitly-interactive ones.
> > >
> > > >   3. In circumstances I haven't pinned down yet, we get the error
> > > >      message "invalid date format: @@2592000 +0000":
> > > >
> > > >          $ git rebase --committer-date-is-author-date --onto branch_K
> > > >          branch_L~1 branch_L
> > > >          $ git checkout --theirs file
> > > >          $ git add file
> > > >          $ git rebase --continue
> > > >          fatal: invalid date format: @@2592000 +0000
> > > >          error: could not commit staged changes.
> > > >
> > > >      This isn't reproducible without --committer-date-is-author-date.
> > > >      More context (the test where it happens) is in [2].
> > >
> > > Interesting.  Do you happen to know if this started happening with
> > > ra/rebase-i-more-options, or did it just become an issue with
> > > en/rebase-backend?  I looked around at the link you provided and feel
> > > a bit confused; I'm not sure which test does this or how I'd
> > > reproduce.
> >
> > I'm confused by the test as well. As ra/rebase-i-more-options only touched the
> > sequencer then any bugs would only show up in this test (which runs a
> > non-interactive rebase) once en/rbease-backend switched to that backend. It
> > seems likely that ra/rebase-i-more-options is to blame.
> >
> > Jonathan - do you happen to know if your users create empty commits at all?
> > and if so what do they expect rebase to do with them (and any that become
> > empty when they are rebased) - cf
> > https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>
>
> The double `@` looks very funny. I would be interested in seeing an MCVE.
>
> > > >   4. I suspect the exit status in the "you need to resolve conflicts"
> > > >      case has changed.  With rebase --am, [3] would automatically
> > > >      invoke rebase --abort when conflicts are present, but with rebase
> > > >      --merge it does not.
> > > >
> > > > Known?
> > >
> > > Nope, but I would certainly hope that "you need to resolve conflicts"
> > > would result in a non-zero exit status.  If it doesn't, that sounds
> > > like a bug in the interactive backend that we need to fix.  I'll dig
> > > in.
>
> Yes, exiting with status 0 would be a major bug, and I think it might even
> be a bug that was introduced by me when I re-implemented the core loop of
> the interactive rebase in C.

Ooh, that sounds interesting.  Do you have any more details?  My
simple testing here shows that we exit with status 1, so we shouldn't
have that problem unless perhaps there was something else in next
(ra/rebase-i-more-options??) or some other special conditions that was
causing it.

> But to me it sounds as if 4. is not so about the exit code but about
> aborting immediately. I do not recall seeing --am rebases to abort,
> though, but to exit with error (and I saw the same behavior in interactive
> rebases).

The aborting "immediately" wasn't done by git but by the code calling
git; following Jonathan's link you'll see the code they are
complaining about is:

func tryRebase(jirix *jiri.X, project Project, branch string) (bool, error) {
    scm := gitutil.New(jirix, gitutil.RootDirOpt(project.Path))
    if err := scm.Rebase(branch); err != nil {
        err := scm.RebaseAbort()
        return false, err
    }
    return true, nil
}

but of course, the scm.Rebase() and scm.RebaseAbort() definitions
aren't within that file so I have no idea what is really being run.

> We will need to see a reduced concrete example (preferably as a new test
> case) of the described behavior.

Yes, please.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  6:32               ` Elijah Newren
@ 2020-01-16  7:58                 ` Jonathan Nieder
  2020-01-16  8:06                   ` Jonathan Nieder
                                     ` (2 more replies)
  2020-01-16 10:48                 ` Johannes Schindelin
  1 sibling, 3 replies; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-16  7:58 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren wrote:

>                                          Then omit calling the
> post-commit hook and it behaves the same as the am backend and no one
> in the world notices because no one in the world uses or cares about
> that hook except a few people at Google who happen to be used to the
> am-backend

Just responding to this part: I know this was a bit of thinking out
loud, but the "just a few people at Google" bit is counter-productive.
The search Emily ran
<https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>
shows that it's fairly common to use a post-commit hook for
deployment, with scripts like

	#!/bin/bash
	unset GIT_INDEX_FILE
	git --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -f

or

	#!/bin/bash
	# Sync gh-pages branch with master
	#########################################
	git checkout gh-pages
	git rm -rf -q .
	git checkout master -- .
	git add .
	git commit -am "Syncing gh-pages with master"
	git checkout master

And I'm not saying that selfishly --- obviously, from a selfish
perspective, what you're proposing would change behavior the least and
I'd end up with happy users. :)  I'm just trying to help with updating
the list's collective model of user behavior.

(Actually, I want to remove jiri's post-commit hook --- so it is only
the example that revealed this behavior change and is not my
motivation for continuing to chime in in this thread.)

The deployment examples above seem like examples where the user would
want the script to run on "git am" (and on "git merge --ff-only", for
that matter) but not on the intermediate commits in "git rebase",
since when rebasing a multi-commit series, deploying earlier rebased
commits would cause the deployment to lose the benefit of later fixes.

[...]
> Ooh, that sounds interesting.  Do you have any more details?  My
> simple testing here shows that we exit with status 1, so we shouldn't
> have that problem unless perhaps there was something else in next
> (ra/rebase-i-more-options??) or some other special conditions that was
> causing it.

I can set aside some more time to investigate that one early next
week.

Thanks for the quick answers --- it's been very helpful.

Sincerely,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  7:58                 ` Jonathan Nieder
@ 2020-01-16  8:06                   ` Jonathan Nieder
  2020-01-16 16:18                     ` Elijah Newren
  2020-01-16 15:35                   ` Elijah Newren
  2020-01-16 20:05                   ` Junio C Hamano
  2 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-16  8:06 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

Jonathan Nieder wrote:

> 	#!/bin/bash
> 	# Sync gh-pages branch with master
> 	#########################################
> 	git checkout gh-pages
> 	git rm -rf -q .
> 	git checkout master -- .
> 	git add .
> 	git commit -am "Syncing gh-pages with master"
> 	git checkout master

Correction: I misread this one.  It was guarded with

	if [ `git rev-parse --abbrev-ref HEAD` == "master" ]; then
		...
	fi

which makes all the difference.  Another example is to compute ctags:

	#!/bin/sh

	~/bin/hook_ctags.sh >/dev/null 2>&1 &

Here's a fun one that updates a timesheet:
https://github.com/GokhanArik/git-hooks-timesheet

Sorry for the noise,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  6:32               ` Elijah Newren
  2020-01-16  7:58                 ` Jonathan Nieder
@ 2020-01-16 10:48                 ` Johannes Schindelin
  1 sibling, 0 replies; 161+ messages in thread
From: Johannes Schindelin @ 2020-01-16 10:48 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Phillip Wood, Jonathan Nieder, Elijah Newren via GitGitGadget,
	Git Mailing List, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Hi Elijah,

On Wed, 15 Jan 2020, Elijah Newren wrote:

> On Sun, Jan 12, 2020 at 9:59 AM Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> >
> > On Sat, 11 Jan 2020, Phillip Wood wrote:
> >
> > > On 11/01/2020 01:16, Elijah Newren wrote:
> > > >
> > > > On Fri, Jan 10, 2020 at 3:14 PM Jonathan Nieder
> > > > <jrnieder@gmail.com> wrote:
> >
> > > > >   4. I suspect the exit status in the "you need to resolve conflicts"
> > > > >      case has changed.  With rebase --am, [3] would automatically
> > > > >      invoke rebase --abort when conflicts are present, but with rebase
> > > > >      --merge it does not.
> > > > >
> > > > > Known?
> > > >
> > > > Nope, but I would certainly hope that "you need to resolve
> > > > conflicts" would result in a non-zero exit status.  If it doesn't,
> > > > that sounds like a bug in the interactive backend that we need to
> > > > fix.  I'll dig in.
> >
> > Yes, exiting with status 0 would be a major bug, and I think it might
> > even be a bug that was introduced by me when I re-implemented the core
> > loop of the interactive rebase in C.
>
> Ooh, that sounds interesting.  Do you have any more details?  My simple
> testing here shows that we exit with status 1, so we shouldn't have that
> problem unless perhaps there was something else in next
> (ra/rebase-i-more-options??) or some other special conditions that was
> causing it.

Oh, I did not mean to say that it exits with status 0. I meant to say that
_if_ it does, it would be terrible, and that my suspicion would be that
the re-implementation in C introduced that bug. But as you say, there is
no bug, so for once, the re-implementation in C is not at fault! ;-)

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  7:58                 ` Jonathan Nieder
  2020-01-16  8:06                   ` Jonathan Nieder
@ 2020-01-16 15:35                   ` Elijah Newren
  2020-01-16 20:05                   ` Junio C Hamano
  2 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-01-16 15:35 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

On Wed, Jan 15, 2020 at 11:58 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Elijah Newren wrote:
>
> >                                          Then omit calling the
> > post-commit hook and it behaves the same as the am backend and no one
> > in the world notices because no one in the world uses or cares about
> > that hook except a few people at Google who happen to be used to the
> > am-backend
>
> Just responding to this part: I know this was a bit of thinking out
> loud, but the "just a few people at Google" bit is counter-productive.

Yes, you're right, I should do better.  Let me try again to express my
thoughts in a more constructive way...

> The search Emily ran
> <https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>
> shows that it's fairly common to use a post-commit hook for
> deployment,

* I actually looked through a dozen or two of those yesterday.  I came
away assuming that many of those might be svn hooks
(http://svnbook.red-bean.com/en/1.7/svn.ref.reposhooks.post-commit.html)
or for other non-git version control systems.  Those that were for git
didn't seem like they'd matter whether or not they were called by
rebase.  Which led me down a thought experiment...

* sequencer.c has accumulated a bit of cruft.  Partially this was
because it's conversion to c was somewhat minimal, e.g. it'd still
fork other processes.  One of these other processes is "git commit".
I think it's been part of the plan all along to remove any forked
processes from sequencer.c and other things that were converted from
shell.

* What if, before being aware of this post-commit hook, someone had
removed the forking of "git commit" from sequencer.c?  I think it
nearly certain that whoever did that wouldn't realize that strict
back-compat would involve adding a call to a post-commit hook when
they did that, and thus all the non-am-based rebases and cherry-picks
would suddenly stop calling that hook.  Would anyone notice?

* People have been using the interactive and merge backends for years
for lots of different rebase types with more and more joining.  We've
never had a report that users were annoyed by it calling the
post-commit hook.  But, on the other hand, rebase-am has run for years
without ever calling the post-commit hook and no one has complained.
I don't believe interactivity is the deciding factor here, as we have
several non-interactive types of rebases implemented by the
interactive backend (--keep-empty, --exec, --rebase-merges,
--preserve-merges, --strategy, --merge) all of which are more similar
in usecase to the am-backend than to the explicitly interactive
usecase from a user perspective (i.e. "Just run on all the commits"
vs. "Let me edit and rearrange and drop and insert and whatnot") and
thus for which I'd assume similar user expectations for what hooks if
any to call.

* When the issue came up, Junio argued that "post-commit" can easily
be argued to mean "whenever a commit is created".  That seemed
eminently reasonable to me.  On the flip side, we do have both
"post-commit" and "post-rewrite" hooks, which could argue that you'd
want to handle brand new commits ("git commit", "git merge")
differently than tweaking existing ones ("git rebase", "git
cherry-pick"), though explicitly interactive rebases are a
monkey-wrench here since they can be used to insert brand new commits
in the middle of rewriting a big pile of commits (suggesting maybe
that only the explicitly interactive rebases should call
"post-commit"?).

* Five different people on this list have chimed in about this
"post-commit" behavior, but I haven't seen a single argument for or
against calling "post-commit" based on actual user expectation for
this kind of usecase (e.g. if done from a clean design).  All I've
seen is folks talking about whether there are backward compatibility
concerns and/or what could be construed by the meaning of words like
"post-commit".  In other words, I don't think any of us have a clue
what is correct behavior if we were designing from scratch.

* Since we don't seem to understand what "correct" behavior is...how
much does or would "incorrect" behavior hurt?  What if we made a
random judgement call about the post-commit hook and got it wrong?
Well...

   * If we changed am to also call the post-commit hook, we have some
early signal that there might be some folks who could comment, though
I haven't yet seen a case where people would be hurt other than a
possible slow-down which the user could probably fix by adjusting
their scripts.  And in such cases it's easy to say "post-commit" means
we call the hook when a new commit is created; if you're doing
something weird where you don't want to do work during the middle of a
rebase your script should check for that case -- it has always been
called by other rebase backends anyway.

   * If we changed sequencer.c to stop forking a git-commit process
and by proxy missed the post-commit hook, I strongly suspect no one
would notice or care.  But if they did, we could just point out how we
have both "post-commit" and "post-rewrite" hooks and that means we
"fixed" our bug of calling the "post-commit" hook from other
rebase/cherry-pick backends in the past by just calling the
post-rewrite hook.  We can tell users they should add a post-rewrite
hook, and, if they want, have it call their post-commit hook one or
more times.

   * If we changed sequencer.c in a way that it stopped calling the
post-commit hook, and someone did notice/care and did have a good
solid argument about correct behavior and that it should involve
calling "post-commit"...then we simply add it and the world is fixed
(though we have to do the explaining that post-commit means whenever a
commit is created mentioned above).  And we provide users with a
workaround for older git versions where we tell them to have the
post-rewrite hook invoke their own post-commit hook for them.


In short, while I don't know what "correct" behavior is other than
that the rebase backends ought to behave the same (or at least all the
am-based rebases and the non-explicitly-interactive rebases
implemented by the interactive backend should all behave the same),
I'm not yet seeing much damage from just picking an answer and
implementing it.

All that said, I'm really, really glad Emily decided to look into this
so I don't have to argue or justify it from anything more than a
"backseat driver" type of perspective.  :-)

> with scripts like
>
>         #!/bin/bash
>         unset GIT_INDEX_FILE
>         git --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -f
>
> or
>
>         #!/bin/bash
>         # Sync gh-pages branch with master
>         #########################################
>         git checkout gh-pages
>         git rm -rf -q .
>         git checkout master -- .
>         git add .
>         git commit -am "Syncing gh-pages with master"
>         git checkout master
>
> And I'm not saying that selfishly --- obviously, from a selfish
> perspective, what you're proposing would change behavior the least and
> I'd end up with happy users. :)  I'm just trying to help with updating
> the list's collective model of user behavior.
>
> (Actually, I want to remove jiri's post-commit hook --- so it is only
> the example that revealed this behavior change and is not my
> motivation for continuing to chime in in this thread.)
>
> The deployment examples above seem like examples where the user would
> want the script to run on "git am" (and on "git merge --ff-only", for
> that matter) but not on the intermediate commits in "git rebase",
> since when rebasing a multi-commit series, deploying earlier rebased
> commits would cause the deployment to lose the benefit of later fixes.

With your correction later in the thread that it's protected by
current branch == "master", this example doesn't concern me as much.
The ctags case is more interesting, but it's currently running on
every commit of rebase using the interactive backend already, and
while it might slow things down to run on every commit it wouldn't
break anything.  Plus, users can easily add a detached-head or
in-the-middle-of-rebase check to their hook to avoid such a slow down
if it is enough for them to notice so I'm not seeing much harm yet.

So, these are definitely interesting cases, but I'm still not seeing
arguments about whether the hook should be called from a user
perspective, just whether users might in the short term be adversely
affected due to assuming they previously built up scripts assuming
only certain types of rebases would ever be run.  (Not that I'm trying
to discount the short term, because it obviously matters, but that I'm
worried we're letting it be the sole deciding factor rather than
thinking of correct behavior and possibly introducing transition plans
and whatnot if needed.)

> [...]
> > Ooh, that sounds interesting.  Do you have any more details?  My
> > simple testing here shows that we exit with status 1, so we shouldn't
> > have that problem unless perhaps there was something else in next
> > (ra/rebase-i-more-options??) or some other special conditions that was
> > causing it.
>
> I can set aside some more time to investigate that one early next
> week.

That'd be great, thanks.

> Thanks for the quick answers --- it's been very helpful.
>
> Sincerely,
> Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  8:06                   ` Jonathan Nieder
@ 2020-01-16 16:18                     ` Elijah Newren
  2020-01-16 20:35                       ` Jonathan Nieder
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-01-16 16:18 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi,

On Thu, Jan 16, 2020 at 12:06 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> which makes all the difference.  Another example is to compute ctags:
>
>         #!/bin/sh
>
>         ~/bin/hook_ctags.sh >/dev/null 2>&1 &

Thought about this ctags case a bit more.  In contrast to the
deployment cases you brought up (which appear to be protected by a
branch check anyway), doesn't this one argue more that post-commit
should be run for all rebase types?  Sure, it'd take more computation
time, but if a user runs "git pull --rebase" and then is frustrated
that all their tags are extremely out of date then they may be kind of
frustrated.

Of course, arguing against myself, we could always tell them to just
implement a post-rewrite hook.

> Here's a fun one that updates a timesheet:
> https://github.com/GokhanArik/git-hooks-timesheet

So...cherry-picking commits will pad your timesheet with duplicate
work on the same topic (because cherry-pick calls sequencer which
calls "git commit"), and so will interactive-based rebases, but
rebase-am (the default rebase in the past) wouldn't.  Fun indeed...

Wouldn't such users view removing the post-commit hook from sequencer
as "harmful to their productivity"?  And shouldn't we add the
post-commit hook to rebase-am in order to "increase their
productivity"?  ;-)

> Sorry for the noise,

No, these were great examples; thanks for pointing them out.  My
commentary about the timesheet is the only thing that is noise here.
:-)

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16  7:58                 ` Jonathan Nieder
  2020-01-16  8:06                   ` Jonathan Nieder
  2020-01-16 15:35                   ` Elijah Newren
@ 2020-01-16 20:05                   ` Junio C Hamano
  2 siblings, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2020-01-16 20:05 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Elijah Newren, Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Jonathan Nieder <jrnieder@gmail.com> writes:

> The deployment examples above seem like examples where the user would
> want the script to run on "git am" (and on "git merge --ff-only", for
> that matter) but not on the intermediate commits in "git rebase",
> since when rebasing a multi-commit series, deploying earlier rebased
> commits would cause the deployment to lose the benefit of later fixes.

Agree.  The same thing can be said to "git am" accepting a series of
multiple patches, though.


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 16:18                     ` Elijah Newren
@ 2020-01-16 20:35                       ` Jonathan Nieder
  2020-01-16 21:30                         ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-16 20:35 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren wrote:

> Thought about this ctags case a bit more.  In contrast to the
> deployment cases you brought up (which appear to be protected by a
> branch check anyway),

To be clear, one of the two deployment cases I mentioned (gh-pages) is
protected by a branch check, but the other (git --work-tree=srvroot
checkout) is not.

>                       doesn't this one argue more that post-commit
> should be run for all rebase types?  Sure, it'd take more computation
> time, but if a user runs "git pull --rebase" and then is frustrated
> that all their tags are extremely out of date then they may be kind of
> frustrated.

Yes.  In fact I think all of these examples argue for having the
post-commit hook run once on rebase completion (and on "git am"
completion, "git merge --ff-only" completion, etc).

Of course it's not hard to find other examples that argue against
that.  We provided a hook "here's a notification that runs after git
commit runs" without tailoring it to a particular need, and on one
hand people came up with various creative ways to use it (yay!) but on
the other hand it's hard to maintain as Git's ways of making new
commits evolve.

> Of course, arguing against myself, we could always tell them to just
> implement a post-rewrite hook.

That's an interesting facet I hadn't thought about before.

It's tempting since it kind of minimizes the blast surface while
providing people a way to keep doing what they had wanted.

Historically "git cherry-pick" ran "git commit", causing it to run the
post-commit hook.  There's some related discussion at
https://lore.kernel.org/git/20f33df8-7ba8-af26-e0c8-16152345c85b@talktalk.net/
of other side effects of having run "git commit".

... oh! git cherry-pick doesn't run post-rewrite.  Interesting.

To summarize what we've discussed so far:

 A. run post-commit hook consistently in rebase --am and --merge modes

    Advantages:
    * consistent with historic "rebase -i" behavior
    * supports hooks like ctags generation that want to update state to
      match what has been committed in the worktree (especially when
      preparing for the rebase to stop due to conflicts)
    * consistent with other commands like "git cherry-pick" that run the
      post-commit hook

    Disadvantages:
    * invokes hooks more often in a setting they are not used to being
      invoked in
    * slows down rebase
    * when post-commit hooks are used for deployment, exposes
      intermediate states in the middle of a rebase to the deployment
      environment
    * inconsistent with "git am"

    Mitigations:
    * could go through the normal warn about change, opt-in config,
      opt-out config cycle to make the change smoother
    * could provide new hooks (e.g., one run after a batch of objects
      is created for applications similar to "git gc --auto", one run
      when HEAD is updated for applications like the ctags one, one
      run on ref update for applications like the deployment case) and
      encourage authors to migrate to them
    * could run the post-commit hook in "git am", too

 B. stop running the post-commit hook in rebase

    Advantages:
    * makes --am and --merge consistent with minimal user impact

    Disadvantages:
    * meaning of the post-commit hook remains a muddle
    * in particular, "git am" and "git rebase" would not be consistent
      with "git cherry-pick"

    Mitigations:
    * could provide new hooks for people to migrate to to replace that
      muddle

 C. stop running the post-commit hook in rebase --merge or commit --amend

    Advantages:
    * produces a consistent definition of post-commit ("it's about new
      commits only")
    * hook authors get the same expressiveness using the post-rewrite hook

    Disadvantages:
    * the change to historical "git commit --amend" behavior is likely
      to be surprising

    Mitigations:
    * could go through the warn, opt-in config, opt-out config cycle

Any bits I've missed?

Thanks,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 20:35                       ` Jonathan Nieder
@ 2020-01-16 21:30                         ` Elijah Newren
  2020-01-16 22:39                           ` Jonathan Nieder
  2020-01-16 23:25                           ` Junio C Hamano
  0 siblings, 2 replies; 161+ messages in thread
From: Elijah Newren @ 2020-01-16 21:30 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

On Thu, Jan 16, 2020 at 12:35 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> > Of course, arguing against myself, we could always tell them to just
> > implement a post-rewrite hook.
>
> That's an interesting facet I hadn't thought about before.
>
> It's tempting since it kind of minimizes the blast surface while
> providing people a way to keep doing what they had wanted.
>
> Historically "git cherry-pick" ran "git commit", causing it to run the

What do you mean "historically"?  I'm pretty sure this piece of code
is shared in the sequencer between rebase -i, rebase --merge, and
cherry-pick, so all three currently call post-commit unless I'm
mistaken.

> post-commit hook.  There's some related discussion at
> https://lore.kernel.org/git/20f33df8-7ba8-af26-e0c8-16152345c85b@talktalk.net/
> of other side effects of having run "git commit".
>
> ... oh! git cherry-pick doesn't run post-rewrite.  Interesting.
>
> To summarize what we've discussed so far:
>
>  A. run post-commit hook consistently in rebase --am and --merge modes

Could you spell these out a bit more?  I think by this option you mean
"Always run post-commit when commits are created", which includes
whether those commits were created by git-commit, git-rebase (any
backend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but
with a glaring exception for fast-import and things that call it, much
like the exception post-rewrite already carves out for it).

Much as I don't see a reason to make rebase --merge different than
rebase --am, I don't see a reason to differentiate between git-merge
and git-commit.  I also don't see why rebase --am should be different
than am.

>     Advantages:
>     * consistent with historic "rebase -i" behavior
>     * supports hooks like ctags generation that want to update state to
>       match what has been committed in the worktree (especially when
>       preparing for the rebase to stop due to conflicts)
>     * consistent with other commands like "git cherry-pick" that run the
>       post-commit hook
>
>     Disadvantages:
>     * invokes hooks more often in a setting they are not used to being
>       invoked in
>     * slows down rebase
>     * when post-commit hooks are used for deployment, exposes
>       intermediate states in the middle of a rebase to the deployment
>       environment
>     * inconsistent with "git am"

I don't want to introduce additional inconsistencies.  git-am and git
merge should be changed at the same time if we go this route.

>     Mitigations:
>     * could go through the normal warn about change, opt-in config,
>       opt-out config cycle to make the change smoother
>     * could provide new hooks (e.g., one run after a batch of objects
>       is created for applications similar to "git gc --auto", one run
>       when HEAD is updated for applications like the ctags one, one
>       run on ref update for applications like the deployment case) and
>       encourage authors to migrate to them
>     * could run the post-commit hook in "git am", too



>  B. stop running the post-commit hook in rebase

AND sometimes in both cherry-pick and git-revert, depending on the
number of commit(s) picked/reverted (see below).

Also, even if we go this route, I think the post-commit hook should be
added to git-merge whenever it creates a merge commit.

>     Advantages:
>     * makes --am and --merge consistent with minimal user impact

...and also makes --interactive and --exec and other cases consistent too.

>     Disadvantages:
>     * meaning of the post-commit hook remains a muddle

Why?  "Commands which create no more than one commit (git-commit,
git-merge, maybe single-commit git-revert or git-cherry-pick) call
post-commit, commands which create several commits derived from others
(git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call
post-rewrite instead for performance reasons" (with the standard
exception for fast-import; also, git-merge could probably call
post-rewrite in fast-forward cases since no new commit is created to
call post-commit on).

Or we could just say that commands which can create multiple commits
(cherry-pick & revert) only call post-rewrite even in cases where they
are only called with one commit (since am and rebase can also be
called on just one commit).

>     * in particular, "git am" and "git rebase" would not be consistent
>       with "git cherry-pick"

Yeah, I'd rather not introduce more inconsistencies; we should pick a
rule and enforce it universally.  Thus, I'd say option B would
probably be thought of as the "always creates at most one commit" vs.
"creates multiple commits" split for post-commit vs. post-rewrite.

>     Mitigations:
>     * could provide new hooks for people to migrate to to replace that
>       muddle
>
>  C. stop running the post-commit hook in rebase --merge or commit --amend
>
>     Advantages:
>     * produces a consistent definition of post-commit ("it's about new
>       commits only")

But cherry-pick never creates "new" commits if commit --amend doesn't,
so we'd also want to turn the post-commit hook off for cherry-pick,
right?  And we'd want to turn post-commit on for git-merge, at least
for non-fast-forward cases since those create new commits.

>     * hook authors get the same expressiveness using the post-rewrite hook

Isn't this also an advantage for case B?  Any reason it was left off up there?

>     Disadvantages:
>     * the change to historical "git commit --amend" behavior is likely
>       to be surprising

That is certainly one issue, but I'd say this case is riddled with
problems and is the most muddled of all.  Once you change git commit
--amend behavior because it's about *new* commits only then you've got
a huge can of worms from rebase -i:

For interactive rebases, we have to avoid the post-commit hook for
anything that is a pick, but if they break or edit and insert new
commits then we have to call the post-commit hook for those.  If the
rebase stops due to a merge conflict, then the next commit created
needs to avoid the post-commit hook.  If they pick to squash or fixup
commits, then we have to deploy rename/break detection to determine
whether the commit is "new" or not and thus whether the post-commit
hook should be triggered for it.  Actually, maybe we need the
rename/break-detection logic when the rebase stops at a conflict too
due to user edits.  And then we start getting into questions about
whether the normal heuristics for rename/break-detection apply or if
"new-ness" should have its own set of heuristics...

Also,  I can't tell if a reverted commit count as "new" or not.  Even
if it does, what about the case of a reverted revert, or higher order
iterations of reverts (which I see occasionally)?

Does this definition mean we should use the git-cherry machinery on
any new commits to search for "newness" (maybe even with an extension
to search across all branches) before we decide whether to call the
post-commit hook?

I think case C is opening up a pandora's box of craziness.

>     Mitigations:
>     * could go through the warn, opt-in config, opt-out config cycle
>
> Any bits I've missed?
>
> Thanks,
> Jonathan


I think it was useful to phrase it this way and list the various
advantages, disadvantages, and mitigations; thanks.

Is there anything I've missed in my additional details?  Anything
sound crazy (beyond what I said I think case C leads to)?


Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 21:30                         ` Elijah Newren
@ 2020-01-16 22:39                           ` Jonathan Nieder
  2020-01-16 23:19                             ` Elijah Newren
  2020-01-16 23:25                           ` Junio C Hamano
  1 sibling, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-01-16 22:39 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren wrote:
> On Thu, Jan 16, 2020 at 12:35 PM Jonathan Nieder <jrnieder@gmail.com> wrote:

>> Historically "git cherry-pick" ran "git commit", causing it to run the
>
> What do you mean "historically"?

Before 356ee4659bb (sequencer: try to commit without forking 'git
commit', 2017-11-24).  See also 4627bc777e9 (sequencer: run
post-commit hook, 2019-10-15).

>                                  I'm pretty sure this piece of code
> is shared in the sequencer between rebase -i, rebase --merge, and
> cherry-pick, so all three currently call post-commit unless I'm
> mistaken.

Yep, that's right.

[...]
>>  A. run post-commit hook consistently in rebase --am and --merge modes
>
> Could you spell these out a bit more?  I think by this option you mean
> "Always run post-commit when commits are created", which includes
> whether those commits were created by git-commit, git-rebase (any
> backend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but
> with a glaring exception for fast-import and things that call it, much
> like the exception post-rewrite already carves out for it).
>
> Much as I don't see a reason to make rebase --merge different than
> rebase --am, I don't see a reason to differentiate between git-merge
> and git-commit.  I also don't see why rebase --am should be different
> than am.

Sure, that matches the general gist behind (A).

[...]
>>  B. stop running the post-commit hook in rebase
>
> AND sometimes in both cherry-pick and git-revert, depending on the
> number of commit(s) picked/reverted (see below).

Interesting.  Doesn't "git cherry-pick A..B" run post-commit after
each commit today?  It would be possible to do (B) and retain that
behavior.

> Also, even if we go this route, I think the post-commit hook should be
> added to git-merge whenever it creates a merge commit.

Agreed.

>>     Advantages:
>>     * makes --am and --merge consistent with minimal user impact
>
> ...and also makes --interactive and --exec and other cases consistent too.

Hm.  My initial thought with (B) was to stop running post-commit in
non-interactive rebase but keep running it in rebase --interactive.

I don't have a strong opinion about this, though.

>>     Disadvantages:
>>     * meaning of the post-commit hook remains a muddle
>
> Why?  "Commands which create no more than one commit (git-commit,
> git-merge, maybe single-commit git-revert or git-cherry-pick) call
> post-commit, commands which create several commits derived from others
> (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call
> post-rewrite instead for performance reasons"

Example: if I run "git revert A..B", following that rule we'd want to
call post-rewrite instead of post-commit.  But post-rewrite gets input

	<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF

so that it can be used to copy data such as notes.  What do I pass in as
<old-sha1>?

Relatedly, if I remember correctly, we don't call post-rewrite in "git
cherry-pick" because cherry-pick is considered to represent a *new*
commit instead of a modified version of the old one.  In C++ terms,
it's a copy constructor instead of a move constructor. ;-)  See
notes.rewrite.* in "git help notes" for more on that subject.

[...]
>>  C. stop running the post-commit hook in rebase --merge or commit --amend
>>
>>     Advantages:
>>     * produces a consistent definition of post-commit ("it's about new
>>       commits only")
>
> But cherry-pick never creates "new" commits if commit --amend doesn't,
> so we'd also want to turn the post-commit hook off for cherry-pick,
> right?

With the copy/modify distinction described above, "git commit --amend"
acts as a "modify" operation in a user's workflow, while "git
cherry-pick" acts as a "copy" operation.

That means that with this definition, post-commit is appropriate to
run in cherry-pick but not in commit --amend.

This matches the distinction used by note rewriting and the
post-rewrite hook.  Looking back at the discussion in
https://lore.kernel.org/git/cover.1266164150.git.trast@student.ethz.ch/
I don't see this covered, so it's possible I'm remembering wrong (a
pointer to the motivating discussion that inspired that patch series
would be welcome).  In the context of note copying, this distinction
ended up being a good decision in practice in most workflows I've
seen.

[...]
>>     Disadvantages:
>>     * the change to historical "git commit --amend" behavior is likely
>>       to be surprising
>
> That is certainly one issue, but I'd say this case is riddled with
> problems and is the most muddled of all.

I'm inclined to agree, based on the "git commit --amend" example.

[...]
> I think it was useful to phrase it this way and list the various
> advantages, disadvantages, and mitigations; thanks.
>
> Is there anything I've missed in my additional details?  Anything
> sound crazy (beyond what I said I think case C leads to)?

To sum it up, I lean toward (B), but (A) is not so bad, either
(especially with the "I need some time to adapt to the change" opt-out
config described under mitigations).

Thanks for patiently working it through with me.

My two cents,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 22:39                           ` Jonathan Nieder
@ 2020-01-16 23:19                             ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-01-16 23:19 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Junio C Hamano, Pavel Roskin, Alban Gruin, SZEDER Gábor

On Thu, Jan 16, 2020 at 2:39 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
> [...]
> >>  B. stop running the post-commit hook in rebase
> >
> > AND sometimes in both cherry-pick and git-revert, depending on the
> > number of commit(s) picked/reverted (see below).
>
> Interesting.  Doesn't "git cherry-pick A..B" run post-commit after
> each commit today?  It would be possible to do (B) and retain that
> behavior.

Sure, it'd be possible to retain the post-commit calls for cherry-pick
but should we?  Isn't the primary reason the issue came up that having
batch-commit operations calling post-commit for each intermediate
commit is ugly/unexpected overhead?  If we're ripping it out of rebase
for that reason, it seems that cherry-pick should get the same
treatment.  I don't want to just move the inconsistencies around, I
want to fix them.

> > Also, even if we go this route, I think the post-commit hook should be
> > added to git-merge whenever it creates a merge commit.
>
> Agreed.

Yay!

> >>     Advantages:
> >>     * makes --am and --merge consistent with minimal user impact
> >
> > ...and also makes --interactive and --exec and other cases consistent too.
>
> Hm.  My initial thought with (B) was to stop running post-commit in
> non-interactive rebase but keep running it in rebase --interactive.
>
> I don't have a strong opinion about this, though.

I'm okay with treating them differently if there's a defining reason
why, but otherwise I think they should all behave the same.  The only
reasons I've seen for differences all fall in category C so far, which
gives me a strong opinion against treating them differently.

> >>     Disadvantages:
> >>     * meaning of the post-commit hook remains a muddle
> >
> > Why?  "Commands which create no more than one commit (git-commit,
> > git-merge, maybe single-commit git-revert or git-cherry-pick) call
> > post-commit, commands which create several commits derived from others
> > (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call
> > post-rewrite instead for performance reasons"
>
> Example: if I run "git revert A..B", following that rule we'd want to
> call post-rewrite instead of post-commit.  But post-rewrite gets input
>
>         <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
>
> so that it can be used to copy data such as notes.  What do I pass in as
> <old-sha1>?
>
> Relatedly, if I remember correctly, we don't call post-rewrite in "git
> cherry-pick" because cherry-pick is considered to represent a *new*
> commit instead of a modified version of the old one.  In C++ terms,
> it's a copy constructor instead of a move constructor. ;-)  See
> notes.rewrite.* in "git help notes" for more on that subject.

Ah, I see.  Yeah, there's some impedance mismatch between the
purpose/definition of the post-rewrite hook and what I think is needed
for B.  Perhaps create a new post-batch-commit hook
("batch-post-commit"?) and have git-rebase, git-am, git-cherry-pick,
git-revert, etc. call it while git-commit and git-merge call
post-commit.  (Or maybe even make git-commit and git-merge also call
post-batch-commit/batch-post-commit but do so with just one commit --
and just note that post-commit's design of not taking any parameters
was a mistake and any place that happens to call it beyond
"git-commit/git-merge" is solely by accident of past implementation
and is not guaranteed to continue.)

I think the realm of "copy constructor vs. move constructor" may make
sense for post-rewrite-hook, but I'm worried that trying to apply that
logic to notification of new commits may lead us into category C
craziness.

> [...]
> >>  C. stop running the post-commit hook in rebase --merge or commit --amend
> >>
> >>     Advantages:
> >>     * produces a consistent definition of post-commit ("it's about new
> >>       commits only")
> >
> > But cherry-pick never creates "new" commits if commit --amend doesn't,
> > so we'd also want to turn the post-commit hook off for cherry-pick,
> > right?
>
> With the copy/modify distinction described above, "git commit --amend"
> acts as a "modify" operation in a user's workflow, while "git
> cherry-pick" acts as a "copy" operation.
>
> That means that with this definition, post-commit is appropriate to
> run in cherry-pick but not in commit --amend.

What if there are conflicts?  Because with conflicts, aren't
cherry-picks more like the 'commit --amend' case since they behave as
a "modify"?  Conflict-dependent invoking of the post-commit hook?

> This matches the distinction used by note rewriting and the
> post-rewrite hook.  Looking back at the discussion in
> https://lore.kernel.org/git/cover.1266164150.git.trast@student.ethz.ch/
> I don't see this covered, so it's possible I'm remembering wrong (a
> pointer to the motivating discussion that inspired that patch series
> would be welcome).  In the context of note copying, this distinction
> ended up being a good decision in practice in most workflows I've
> seen.

Yeah, for note copying that distinction makes a lot of sense to me.
For "notification of new commits", it doesn't seem to jive at all for
me.

> [...]
> >>     Disadvantages:
> >>     * the change to historical "git commit --amend" behavior is likely
> >>       to be surprising
> >
> > That is certainly one issue, but I'd say this case is riddled with
> > problems and is the most muddled of all.
>
> I'm inclined to agree, based on the "git commit --amend" example.
>
> [...]
> > I think it was useful to phrase it this way and list the various
> > advantages, disadvantages, and mitigations; thanks.
> >
> > Is there anything I've missed in my additional details?  Anything
> > sound crazy (beyond what I said I think case C leads to)?
>
> To sum it up, I lean toward (B), but (A) is not so bad, either
> (especially with the "I need some time to adapt to the change" opt-out
> config described under mitigations).

Sounds like we might be starting to converge towards a similar
opinion.  I roughly feel the same with leaning towards something like
(B) _if_ we can address the inconsistencies rather than just shuffle
them around (meaning we also turn off post-commit for interactive
rebase and cherry-pick) and move all of them over to some kind of
batch-commit-notification mechanism.

> Thanks for patiently working it through with me.
>
> My two cents,
> Jonathan

Likewise.  :-)
Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 21:30                         ` Elijah Newren
  2020-01-16 22:39                           ` Jonathan Nieder
@ 2020-01-16 23:25                           ` Junio C Hamano
  2020-01-17  0:51                             ` Elijah Newren
  1 sibling, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-01-16 23:25 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Jonathan Nieder, Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren <newren@gmail.com> writes:

> Why?  "Commands which create no more than one commit (git-commit,
> git-merge, maybe single-commit git-revert or git-cherry-pick) call
> post-commit, commands which create several commits derived from others
> (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call
> post-rewrite instead for performance reasons"

Sounds totally wrong.  post-rewrite is about carrying forward data
that used to be associated with commit X to new commit Y when Y was
created by "rewriting" X (i.e. after Y gets created X no longer has
any purpose other than as a historical curiosity).  There is nothing
"for performance reasons" here.  There should be nothing for "git
revert" to do with post-rewrite (because the resulting commit is not
even a rewrite of the commit that was reverted), and "git
cherry-pick" is not even a rewrite operation (it is to duplicate an
existing commit into another unrelated context, without discarding
the original commit).

I am a bit confused...

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
  2020-01-16 23:25                           ` Junio C Hamano
@ 2020-01-17  0:51                             ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-01-17  0:51 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jonathan Nieder, Johannes Schindelin, Phillip Wood,
	Elijah Newren via GitGitGadget, Git Mailing List, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

On Thu, Jan 16, 2020 at 3:25 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Elijah Newren <newren@gmail.com> writes:
>
> > Why?  "Commands which create no more than one commit (git-commit,
> > git-merge, maybe single-commit git-revert or git-cherry-pick) call
> > post-commit, commands which create several commits derived from others
> > (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call
> > post-rewrite instead for performance reasons"
>
> Sounds totally wrong.  post-rewrite is about carrying forward data
> that used to be associated with commit X to new commit Y when Y was
> created by "rewriting" X (i.e. after Y gets created X no longer has
> any purpose other than as a historical curiosity).  There is nothing
> "for performance reasons" here.  There should be nothing for "git
> revert" to do with post-rewrite (because the resulting commit is not
> even a rewrite of the commit that was reverted), and "git
> cherry-pick" is not even a rewrite operation (it is to duplicate an
> existing commit into another unrelated context, without discarding
> the original commit).

Yep, it was totally wrong; Jonathan clued me in later in the thread.

> I am a bit confused...

No, it was I who was confused.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 00/19] rebase: make the default backend configurable
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (18 preceding siblings ...)
  2020-01-16  6:14       ` [PATCH v4 19/19] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2020-01-17 16:58       ` Phillip Wood
  2020-02-05 21:06         ` Junio C Hamano
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
  20 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-01-17 16:58 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

Hi Elijah

On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> This is a re-roll of en/rebase-backend which has been rebased on v2.25.0 and
> updated to remove the dependence on ra/rebase-i-more-options, and also tries
> to address feedback from Phillip, Junio, and Jonathan. This series does a
> lot of work around making the default rebase backend configurable, and
> switches the default from the am backend to the merge/interactive one.
> 
> Changes since v3:
> 
>   * Rebased on origin/master and updated to remove the dependence on
>     ra/rebase-i-more-options.
>   * Added two new patches at the start of the series.
>   * Split the old first patch into two, while modifying them based on
>     Phillip's feedback (though slightly differently than discussed on the
>     list; instead of making --keep-empty a synonym for --empty=keep, I
>     instead kept backward compatibility by making it a no-op).
>   * I noted the post-commit hook in the differences between backends. Emily
>     is investigating what changes need to happen there, so I merely
>     documented the existing differences.
>   * dropped '-i' from the reflog messages; now they just refer to 'rebase'
> 
> Changes possibly missing from this version, for discussion:
> 
>   * I did not remove the --am option as suggested by Phillip, since Junio and
>     Phillip were still discussing whether it is wanted/needed. >   * I did not address the last two items Jonathan brought up as I 
couldn't
>     find enough information to reproduce or understand the problems.

I think I've got a fix for the failure with 
--committer-date-is-author-date that Jonathan reported which I'll post 
next week - the bug was not in this series, it was just exposed by it. 
I'll try and read through this series next week as well.

Best Wishes

Phillip

> 
> Elijah Newren (19):
>    git-rebase.txt: update description of --allow-empty-message
>    t3404: directly test the behavior of interest
>    rebase (interactive-backend): make --keep-empty the default
>    rebase (interactive-backend): fix handling of commits that become
>      empty
>    t3406: simplify an already simple test
>    rebase, sequencer: remove the broken GIT_QUIET handling
>    rebase: make sure to pass along the quiet flag to the sequencer
>    rebase: fix handling of restrict_revision
>    t3432: make these tests work with either am or merge backends
>    rebase: allow more types of rebases to fast-forward
>    git-rebase.txt: add more details about behavioral differences of
>      backends
>    rebase: move incompatibility checks between backend options a bit
>      earlier
>    rebase: add an --am option
>    git-prompt: change the prompt for interactive-based rebases
>    rebase: drop '-i' from the reflog for interactive-based rebases
>    rebase tests: mark tests specific to the am-backend with --am
>    rebase tests: repeat some tests using the merge backend instead of am
>    rebase: make the backend configurable via config setting
>    rebase: change the default backend from "am" to "merge"
> 
>   Documentation/config/rebase.txt        |   8 ++
>   Documentation/git-rebase.txt           | 150 +++++++++++++++++---
>   builtin/rebase.c                       | 186 +++++++++++++++++++------
>   contrib/completion/git-prompt.sh       |   6 +-
>   rebase-interactive.c                   |   7 +-
>   rebase-interactive.h                   |   2 +-
>   sequencer.c                            |  84 ++++++-----
>   sequencer.h                            |   3 +-
>   t/t3400-rebase.sh                      |  36 ++++-
>   t/t3401-rebase-and-am-rename.sh        |   4 +-
>   t/t3404-rebase-interactive.sh          |  19 +--
>   t/t3406-rebase-message.sh              |  19 ++-
>   t/t3407-rebase-abort.sh                |   6 +-
>   t/t3420-rebase-autostash.sh            |   2 +-
>   t/t3421-rebase-topology-linear.sh      |  16 +--
>   t/t3424-rebase-empty.sh                | 108 ++++++++++++++
>   t/t3425-rebase-topology-merges.sh      |   8 +-
>   t/t3427-rebase-subtree.sh              |  12 +-
>   t/t3432-rebase-fast-forward.sh         |  54 ++++---
>   t/t5407-post-rewrite-hook.sh           |  12 +-
>   t/t5520-pull.sh                        |  27 +++-
>   t/t6047-diff3-conflict-markers.sh      |  13 +-
>   t/t7512-status-help.sh                 |  12 +-
>   t/t9106-git-svn-commit-diff-clobber.sh |   3 +-
>   t/t9903-bash-prompt.sh                 |   8 +-
>   25 files changed, 595 insertions(+), 210 deletions(-)
>   create mode 100755 t/t3424-rebase-empty.sh
> 
> 
> base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v4
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v4
> Pull-Request: https://github.com/git/git/pull/679
> 
> Range-diff vs v3:
> 
>    -:  ---------- >  1:  3ea48d5394 git-rebase.txt: update description of --allow-empty-message
>    -:  ---------- >  2:  10fdd162a0 t3404: directly test the behavior of interest
>    1:  1c2b77e94d !  3:  179f82ab83 rebase: extend the options for handling of empty commits
>       @@ -1,49 +1,46 @@
>        Author: Elijah Newren <newren@gmail.com>
>        
>       -    rebase: extend the options for handling of empty commits
>       +    rebase (interactive-backend): make --keep-empty the default
>        
>       -    Extend the interactive machinery with the ability to handle the full
>       -    spread of options for how to handle commits that either start or become
>       -    empty (by "become empty" I mean the changes in a commit are a subset of
>       -    changes that exist upstream, so the net effect of applying the commit is
>       -    no changes).  Introduce a new command line flag for selecting the
>       -    desired behavior:
>       -        --empty={drop,keep,ask}
>       -    with the definitions:
>       -        drop: drop empty commits
>       -        keep: keep empty commits
>       -        ask:  provide the user a chance to interact and pick what to do with
>       -              empty commits on a case-by-case basis
>       +    Different rebase backends have different treatment for commits which
>       +    start empty (i.e. have no changes relative to their parent), and the
>       +    --keep-empty option was added at some point to allow adjusting behavior
>       +    for the interactive backend.  The handling of commits which start empty
>       +    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
>       +    --allow-empty-message the default, 2018-06-27), which pointed out that
>       +    the behavior for various backends is often more happenstance than
>       +    design.  The specific change made in that commit is actually quite
>       +    relevant as well and much of the logic there directly applies here.
>        
>       -    Note that traditionally, am-based rebases have always dropped commits
>       -    that either started or became empty, while interactive-based rebases
>       -    have defaulted to ask (and provided an option to keep commits that
>       -    started empty).  This difference made sense since users of an am-based
>       -    rebase just wanted to quickly batch apply a sequence of commits, while
>       -    users editing a todo list will likely want the chance to interact and
>       -    handle unusual cases on a case-by-case basis.  However, not all rebases
>       -    using the interactive machinery are explicitly interactive anymore.  In
>       -    particular --merge was always meant to behave more like --am: just
>       -    rebase a batch of commits without popping up a todo list.
>       +    It makes a lot of sense in 'git commit' to error out on the creation of
>       +    empty commits, unless an override flag is provided.  However, once
>       +    someone determines that there is a rare case that merits using the
>       +    manual override to create such a commit, it is somewhere between
>       +    annoying and harmful to have to take extra steps to keep such
>       +    intentional commits around.  Granted, empty commits are quite rare,
>       +    which is why handling of them doesn't get considered much and folks tend
>       +    to defer to existing (accidental) behavior and assume there was a reason
>       +    for it, leading them to just add flags (--keep-empty in this case) that
>       +    allow them to override the bad defaults.  Fix the interactive backend so
>       +    that --keep-empty is the default, much like we did with
>       +    --allow-empty-message.  The am backend should also be fixed to have
>       +    --keep-empty semantics for commits that start empty, but that is not
>       +    included in this patch other than a testcase documenting the failure.
>        
>       -    If the --empty flag is not specified, pick defaults as follows:
>       -        explicitly interactive: ask
>       -        --exec: keep (exec is about checking existing commits, and often
>       -                      used without actually changing the base.  Thus the
>       -                      expectation is that the user doesn't necessarily want
>       -                      anything to change; they just want to test).
>       -        otherwise: drop
>       -
>       -    Also, this commit makes --keep-empty just imply --empty=keep, and hides
>       -    it from help so that we aren't confusing users with different ways to do
>       -    the same thing.  (I could have added a --drop-empty flag, but then that
>       -    invites users to specify both --keep-empty and --drop-empty and we have
>       -    to add sanity checking around that; it seems cleaner to have a single
>       -    multi-valued option.)  This actually fixes --keep-empty too; previously,
>       -    it only meant to sometimes keep empty commits, in particular commits
>       -    which started empty would be kept.  But it would still error out and ask
>       -    the user what to do with commits that became empty.  Now it keeps empty
>       -    commits, as instructed.
>       +    Note that there was one test in t3421 which appears to have been written
>       +    expecting --keep-empty to not be the default as correct behavior.  This
>       +    test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
>       +    empty commits", 2013-06-06), which was part of a series focusing on
>       +    rebase topology and which had an interesting original cover letter at
>       +    https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
>       +    which noted
>       +        Your input especially appreciated on whether you agree with the
>       +        intent of the test cases.
>       +    and then went into a long example about how one of the many tests added
>       +    had several questions about whether it was correct.  As such, I believe
>       +    most the tests in that series were about testing rebase topology with as
>       +    many different flags as possible and were not trying to state in general
>       +    how those flags should behave otherwise.
>        
>            Signed-off-by: Elijah Newren <newren@gmail.com>
>        
>       @@ -51,126 +48,62 @@
>         --- a/Documentation/git-rebase.txt
>         +++ b/Documentation/git-rebase.txt
>        @@
>       - 	original branch. The index and working tree are also left
>         	unchanged as a result.
>         
>       -+--empty={drop,keep,ask}::
>       -+	How to handle commits that become empty (because they contain a
>       -+	subset of already upstream changes) or start empty.  With drop
>       -+	(the default), commits that start or become empty are dropped.
>       -+	With keep (implied by --exec), such commits are kept.  With ask
>       -+	(implied by --interactive), the rebase will halt when an empty
>       -+	commit is applied allowing you to choose whether to drop it or
>       -+	commit it.  Also with ask, if the rebase is interactive then
>       -+	commits which start empty will be commented out in the todo
>       -+	action list (giving you a chance to uncomment).
>       -++
>       -+Note that this has no effect on commits which are already upstream (as
>       -+can be checked via `git log --cherry-mark ...`), which are always
>       -+dropped by rebase.
>       -++
>       -+See also INCOMPATIBLE OPTIONS below.
>       -+
>         --keep-empty::
>        -	Keep the commits that do not change anything from its
>        -	parents in the result.
>       -+	Deprecated alias for what is now known as --empty=keep.
>       ++	No-op.  Rebasing commits that started empty (had no change
>       ++	relative to their parent) used to fail and this option would
>       ++	override that behavior, allowing commits with empty changes to
>       ++	be rebased.  Now commits with no changes do not cause rebasing
>       ++	to halt.
>         +
>       - See also INCOMPATIBLE OPTIONS below.
>       -
>       -@@
>       -  * --interactive
>       -  * --exec
>       -  * --keep-empty
>       -+ * --empty=
>       -  * --edit-todo
>       -  * --root when used in combination with --onto
>       -
>       -@@
>       -  * --preserve-merges and --ignore-whitespace
>       -  * --preserve-merges and --committer-date-is-author-date
>       -  * --preserve-merges and --ignore-date
>       -+ * --preserve-merges and --empty=
>       -  * --keep-base and --onto
>       -  * --keep-base and --root
>       +-See also INCOMPATIBLE OPTIONS below.
>       ++See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
>         
>       + --allow-empty-message::
>       + 	No-op.  Rebasing commits with an empty message used to fail
>        @@
>       + Empty commits
>       + ~~~~~~~~~~~~~
>         
>       - There are some subtle differences how the backends behave.
>       -
>       --Empty commits
>       --~~~~~~~~~~~~~
>       --
>        -The am backend drops any "empty" commits, regardless of whether the
>        -commit started empty (had no changes relative to its parent to
>        -start with) or ended empty (all changes were already applied
>        -upstream in other commits).
>       --
>       ++The am backend unfortunately drops intentionally empty commits, i.e.
>       ++commits that started empty, though these are rare in practice.  It
>       ++also drops commits that become empty and has no option for controlling
>       ++this behavior.
>       +
>        -The interactive backend drops commits by default that
>        -started empty and halts if it hits a commit that ended up empty.
>        -The `--keep-empty` option exists for the interactive backend to allow
>        -it to keep commits that started empty.
>       --
>       ++The interactive backend keeps intentionally empty commits.
>       ++Unfortunately, it always halts whenever it runs across a commit that
>       ++becomes empty, even when the rebase is not explicitly interactive.
>       +
>         Directory rename detection
>         ~~~~~~~~~~~~~~~~~~~~~~~~~~
>       -
>        
>         diff --git a/builtin/rebase.c b/builtin/rebase.c
>         --- a/builtin/rebase.c
>         +++ b/builtin/rebase.c
>       -@@
>       - 	REBASE_PRESERVE_MERGES
>       - };
>       -
>       -+enum empty_type {
>       -+	EMPTY_UNSPECIFIED = -1,
>       -+	EMPTY_DROP,
>       -+	EMPTY_KEEP,
>       -+	EMPTY_ASK
>       -+};
>       -+
>       - struct rebase_options {
>       - 	enum rebase_type type;
>       -+	enum empty_type empty;
>       - 	const char *state_dir;
>       - 	struct commit *upstream;
>       - 	const char *upstream_name;
>        @@
>         	const char *action;
>         	int signoff;
>         	int allow_rerere_autoupdate;
>        -	int keep_empty;
>         	int autosquash;
>       - 	int ignore_whitespace;
>         	char *gpg_sign_opt;
>       -@@
>       -
>       - #define REBASE_OPTIONS_INIT {			  	\
>       - 		.type = REBASE_UNSPECIFIED,	  	\
>       -+		.empty = EMPTY_UNSPECIFIED,	  	\
>       - 		.flags = REBASE_NO_QUIET, 		\
>       - 		.git_am_opts = ARGV_ARRAY_INIT,		\
>       - 		.git_format_patch_opt = STRBUF_INIT	\
>       -@@
>       - 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
>       - 	replay.allow_empty = 1;
>       - 	replay.allow_empty_message = opts->allow_empty_message;
>       -+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
>       -+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
>       -+	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
>       -+					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
>       - 	replay.verbose = opts->flags & REBASE_VERBOSE;
>       - 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
>       - 	replay.committer_date_is_author_date =
>       + 	int autostash;
>        @@
>         
>         	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
>         
>        -	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
>       -+	flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0;
>       -+	flags |= (opts->empty == EMPTY_ASK &&
>       -+		  opts->flags & REBASE_INTERACTIVE_EXPLICIT) ?
>       -+			TODO_LIST_ASK_EMPTY : 0;
>         	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
>         	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
>         	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
>       @@ -183,10 +116,8 @@
>        +{
>        +	struct rebase_options *opts = opt->value;
>        +
>       -+	BUG_ON_OPT_NEG(unset);
>        +	BUG_ON_OPT_ARG(arg);
>        +
>       -+	opts->empty = EMPTY_KEEP;
>        +	opts->type = REBASE_INTERACTIVE;
>        +	return 0;
>        +}
>       @@ -201,62 +132,28 @@
>        -		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
>        +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>        +			N_("(DEPRECATED) keep empty commits"),
>       -+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
>       ++			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
>        +			parse_opt_keep_empty },
>       - 		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
>       - 			 N_("allow commits with empty messages")),
>       - 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
>       + 		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
>       + 			   N_("allow commits with empty messages"),
>       + 			   PARSE_OPT_HIDDEN),
>        @@
>         		opts->allow_rerere_autoupdate ?
>         			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
>         			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
>        -	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
>       -+	add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : "");
>         	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
>         	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
>         	add_var(&script_snippet, "cmd", opts->cmd);
>       -@@
>       - 	return 0;
>       - }
>       -
>       -+static enum empty_type parse_empty_value(const char *value)
>       -+{
>       -+	if (!strcasecmp(value, "drop"))
>       -+		return EMPTY_DROP;
>       -+	else if (!strcasecmp(value, "keep"))
>       -+		return EMPTY_KEEP;
>       -+	else if (!strcasecmp(value, "ask"))
>       -+		return EMPTY_ASK;
>       -+
>       -+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
>       -+}
>       -+
>       -+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>       -+{
>       -+	struct rebase_options *options = opt->value;
>       -+	enum empty_type value = parse_empty_value(arg);
>       -+
>       -+	BUG_ON_OPT_NEG(unset);
>       -+
>       -+	options->empty = value;
>       -+	return 0;
>       -+}
>       -+
>       - static void NORETURN error_on_missing_default_upstream(void)
>       - {
>       - 	struct branch *current_branch = branch_get(NULL);
>        @@
>         				 "ignoring them"),
>         			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>         		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
>        -		OPT_BOOL('k', "keep-empty", &options.keep_empty,
>        -			 N_("preserve empty commits during rebase")),
>       -+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
>       -+			       N_("how to handle empty commits"),
>       -+			       PARSE_OPT_NONEG, parse_opt_empty),
>        +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>        +			N_("(DEPRECATED) keep empty commits"),
>       -+			PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
>       ++			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
>        +			parse_opt_keep_empty },
>         		OPT_BOOL(0, "autosquash", &options.autosquash,
>         			 N_("move commits that begin with "
>       @@ -267,26 +164,10 @@
>         
>        -	if (options.keep_empty)
>        -		imply_interactive(&options, "--keep-empty");
>       -+	if (options.empty != EMPTY_UNSPECIFIED)
>       -+		imply_interactive(&options, "--empty");
>       -
>       +-
>         	if (gpg_sign) {
>         		free(options.gpg_sign_opt);
>       -@@
>       - 		break;
>       - 	}
>       -
>       -+	if (options.empty == EMPTY_UNSPECIFIED) {
>       -+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
>       -+			options.empty = EMPTY_ASK;
>       -+		else if (exec.nr > 0)
>       -+			options.empty = EMPTY_KEEP;
>       -+		else
>       -+			options.empty = EMPTY_DROP;
>       -+	}
>       - 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
>       - 		die(_("--reschedule-failed-exec requires "
>       - 		      "--exec or --interactive"));
>       + 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
>        
>         diff --git a/rebase-interactive.c b/rebase-interactive.c
>         --- a/rebase-interactive.c
>       @@ -296,19 +177,22 @@
>         }
>         
>        -void append_todo_help(unsigned keep_empty, int command_count,
>       -+void append_todo_help(unsigned no_ask_empty, int command_count,
>       ++void append_todo_help(int command_count,
>         		      const char *shortrevisions, const char *shortonto,
>         		      struct strbuf *buf)
>         {
>        @@
>       + 			"the rebase will be aborted.\n\n");
>         
>         	strbuf_add_commented_lines(buf, msg, strlen(msg));
>       -
>       +-
>        -	if (!keep_empty) {
>       -+	if (!no_ask_empty) {
>       - 		msg = _("Note that empty commits are commented out");
>       - 		strbuf_add_commented_lines(buf, msg, strlen(msg));
>       - 	}
>       +-		msg = _("Note that empty commits are commented out");
>       +-		strbuf_add_commented_lines(buf, msg, strlen(msg));
>       +-	}
>       + }
>       +
>       + int edit_todo_list(struct repository *r, struct todo_list *todo_list,
>        
>         diff --git a/rebase-interactive.h b/rebase-interactive.h
>         --- a/rebase-interactive.h
>       @@ -318,7 +202,7 @@
>         struct todo_list;
>         
>        -void append_todo_help(unsigned keep_empty, int command_count,
>       -+void append_todo_help(unsigned no_ask_empty, int command_count,
>       ++void append_todo_help(int command_count,
>         		      const char *shortrevisions, const char *shortonto,
>         		      struct strbuf *buf);
>         int edit_todo_list(struct repository *r, struct todo_list *todo_list,
>       @@ -327,157 +211,51 @@
>         --- a/sequencer.c
>         +++ b/sequencer.c
>        @@
>       - static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
>       - static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
>       - static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
>       -+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
>       -+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
>       -+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty")
>       -
>       - static int git_sequencer_config(const char *k, const char *v, void *cb)
>       + 		       struct replay_opts *opts,
>       + 		       struct commit *commit)
>         {
>       +-	int index_unchanged, empty_commit;
>       ++	int index_unchanged, originally_empty;
>       +
>       + 	/*
>       + 	 * Three cases:
>        @@
>       - 	empty_commit = is_original_commit_empty(commit);
>       - 	if (empty_commit < 0)
>       - 		return empty_commit;
>       + 	if (opts->keep_redundant_commits)
>       + 		return 1;
>       +
>       +-	empty_commit = is_original_commit_empty(commit);
>       +-	if (empty_commit < 0)
>       +-		return empty_commit;
>        -	if (!empty_commit)
>       -+	if (!empty_commit || opts->ask_on_initially_empty)
>       ++	originally_empty = is_original_commit_empty(commit);
>       ++	if (originally_empty < 0)
>       ++		return originally_empty;
>       ++	if (!originally_empty)
>         		return 0;
>         	else
>         		return 1;
>       -@@
>       - 	char *author = NULL;
>       - 	struct commit_message msg = { NULL, NULL, NULL, NULL };
>       - 	struct strbuf msgbuf = STRBUF_INIT;
>       --	int res, unborn = 0, reword = 0, allow;
>       -+	int res, unborn = 0, reword = 0, allow, drop_commit;
>       -
>       - 	if (opts->no_commit) {
>       - 		/*
>       -@@
>       - 		goto leave;
>       - 	}
>       -
>       --	allow = allow_empty(r, opts, commit);
>       --	if (allow < 0) {
>       --		res = allow;
>       --		goto leave;
>       --	} else if (allow)
>       --		flags |= ALLOW_EMPTY;
>       --	if (!opts->no_commit) {
>       -+	drop_commit = 0;
>       -+	if (opts->drop_redundant_commits && is_index_unchanged(r)) {
>       -+		drop_commit = 1;
>       -+		fprintf(stderr, _("No changes -- Patch already applied."));
>       -+	} else {
>       -+		allow = allow_empty(r, opts, commit);
>       -+		if (allow < 0) {
>       -+			res = allow;
>       -+			goto leave;
>       -+		} else if (allow) {
>       -+			flags |= ALLOW_EMPTY;
>       -+		}
>       -+	}
>       -+	if (!opts->no_commit && !drop_commit) {
>       - 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
>       - 			res = do_commit(r, msg_file, author, opts, flags);
>       - 		else
>       -@@
>       - 	else if (!strcmp(key, "options.allow-empty-message"))
>       - 		opts->allow_empty_message =
>       - 			git_config_bool_or_int(key, value, &error_flag);
>       -+	else if (!strcmp(key, "options.drop-redundant-commits"))
>       -+		opts->drop_redundant_commits =
>       -+			git_config_bool_or_int(key, value, &error_flag);
>       - 	else if (!strcmp(key, "options.keep-redundant-commits"))
>       - 		opts->keep_redundant_commits =
>       - 			git_config_bool_or_int(key, value, &error_flag);
>       -+	else if (!strcmp(key, "options.ask_on_initially_empty"))
>       -+		opts->ask_on_initially_empty =
>       -+			git_config_bool_or_int(key, value, &error_flag);
>       - 	else if (!strcmp(key, "options.signoff"))
>       - 		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
>       - 	else if (!strcmp(key, "options.record-origin"))
>       -@@
>       - 		if (file_exists(rebase_path_reschedule_failed_exec()))
>       - 			opts->reschedule_failed_exec = 1;
>       -
>       -+		if (file_exists(rebase_path_drop_redundant_commits()))
>       -+			opts->drop_redundant_commits = 1;
>       -+
>       -+		if (file_exists(rebase_path_keep_redundant_commits()))
>       -+			opts->keep_redundant_commits = 1;
>       -+
>       -+		if (file_exists(rebase_path_ask_on_initially_empty()))
>       -+			opts->ask_on_initially_empty = 1;
>       -+
>       - 		read_strategy_opts(opts, &buf);
>       - 		strbuf_release(&buf);
>       -
>       -@@
>       - 		write_file(rebase_path_cdate_is_adate(), "%s", "");
>       - 	if (opts->ignore_date)
>       - 		write_file(rebase_path_ignore_date(), "%s", "");
>       -+	if (opts->drop_redundant_commits)
>       -+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
>       -+	if (opts->keep_redundant_commits)
>       -+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
>       -+	if (opts->ask_on_initially_empty)
>       -+		write_file(rebase_path_ask_on_initially_empty(), "%s", "");
>       - 	if (opts->reschedule_failed_exec)
>       - 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
>       -
>       -@@
>       - 	if (opts->allow_empty_message)
>       - 		res |= git_config_set_in_file_gently(opts_file,
>       - 				"options.allow-empty-message", "true");
>       -+	if (opts->drop_redundant_commits)
>       -+		res |= git_config_set_in_file_gently(opts_file,
>       -+				"options.drop-redundant-commits", "true");
>       - 	if (opts->keep_redundant_commits)
>       - 		res |= git_config_set_in_file_gently(opts_file,
>       - 				"options.keep-redundant-commits", "true");
>       -+	if (opts->ask_on_initially_empty)
>       -+		res |= git_config_set_in_file_gently(opts_file,
>       -+				"options.ask_on_initially_empty", "true");
>       - 	if (opts->signoff)
>       - 		res |= git_config_set_in_file_gently(opts_file,
>       - 					"options.signoff", "true");
>        @@
>         				   struct rev_info *revs, struct strbuf *out,
>         				   unsigned flags)
>         {
>        -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
>       -+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
>       -+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
>         	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
>         	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
>         	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
>       -@@
>       - 		is_empty = is_original_commit_empty(commit);
>       - 		if (!is_empty && (commit->object.flags & PATCHSAME))
>       - 			continue;
>       -+		if (is_empty && drop_empty)
>       -+			continue;
>       -
>       - 		strbuf_reset(&oneline);
>       - 		pretty_print_commit(pp, commit, &oneline);
>        @@
>         		if (!to_merge) {
>         			/* non-merge commit: easy case */
>         			strbuf_reset(&buf);
>        -			if (!keep_empty && is_empty)
>       -+			if (is_empty && ask_empty)
>       - 				strbuf_addf(&buf, "%c ", comment_line_char);
>       +-				strbuf_addf(&buf, "%c ", comment_line_char);
>         			strbuf_addf(&buf, "%s %s %s", cmd_pick,
>         				    oid_to_hex(&commit->object.oid),
>       + 				    oneline.buf);
>        @@
>         	struct pretty_print_context pp = {0};
>         	struct rev_info revs;
>         	struct commit *commit;
>        -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
>       -+	int drop_empty = flags & TODO_LIST_DROP_EMPTY;
>       -+	int ask_empty = flags & TODO_LIST_ASK_EMPTY;
>         	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
>         	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
>         
>       @@ -491,19 +269,16 @@
>         		if (!is_empty && (commit->object.flags & PATCHSAME))
>         			continue;
>        -		if (!keep_empty && is_empty)
>       -+		if (is_empty && drop_empty)
>       -+			continue;
>       -+		if (is_empty && ask_empty)
>       - 			strbuf_addf(out, "%c ", comment_line_char);
>       +-			strbuf_addf(out, "%c ", comment_line_char);
>         		strbuf_addf(out, "%s %s ", insn,
>         			    oid_to_hex(&commit->object.oid));
>       + 		pretty_print_commit(&pp, commit, out);
>        @@
>         
>         	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
>         	if (flags & TODO_LIST_APPEND_TODO_HELP)
>        -		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
>       -+		append_todo_help(!(flags & TODO_LIST_ASK_EMPTY),
>       -+				 count_commands(todo_list),
>       ++		append_todo_help(count_commands(todo_list),
>         				 shortrevisions, shortonto, &buf);
>         
>         	res = write_message(buf.buf, buf.len, file, 0);
>       @@ -511,16 +286,6 @@
>         diff --git a/sequencer.h b/sequencer.h
>         --- a/sequencer.h
>         +++ b/sequencer.h
>       -@@
>       - 	int allow_rerere_auto;
>       - 	int allow_empty;
>       - 	int allow_empty_message;
>       -+	int drop_redundant_commits;
>       - 	int keep_redundant_commits;
>       -+	int ask_on_initially_empty;
>       - 	int verbose;
>       - 	int quiet;
>       - 	int reschedule_failed_exec;
>        @@
>         int sequencer_skip(struct repository *repo, struct replay_opts *opts);
>         int sequencer_remove_state(struct replay_opts *opts);
>       @@ -530,19 +295,34 @@
>         #define TODO_LIST_SHORTEN_IDS (1U << 1)
>         #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
>         #define TODO_LIST_REBASE_MERGES (1U << 3)
>       -@@
>       -  * `--onto`, we do not want to re-generate the root commits.
>       -  */
>       - #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
>       -+#define TODO_LIST_DROP_EMPTY (1U << 7)
>       -+#define TODO_LIST_ASK_EMPTY (1U << 8)
>       -
>       -
>       - int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
>        
>         diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
>         --- a/t/t3421-rebase-topology-linear.sh
>         +++ b/t/t3421-rebase-topology-linear.sh
>       +@@
>       + test_run_rebase () {
>       + 	result=$1
>       + 	shift
>       +-	test_expect_$result "rebase $* drops empty commit" "
>       ++	test_expect_$result "rebase $* keeps begin-empty commits" "
>       + 		reset_rebase &&
>       +-		git rebase $* c l &&
>       +-		test_cmp_rev c HEAD~2 &&
>       +-		test_linear_range 'd l' c..
>       ++		git rebase $* j l &&
>       ++		test_cmp_rev c HEAD~4 &&
>       ++		test_linear_range 'j d k l' c..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase failure ''
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       +-test_have_prereq !REBASE_P || test_run_rebase success -p
>       ++test_have_prereq !REBASE_P || test_run_rebase failure -p
>       +
>       + test_run_rebase () {
>       + 	result=$1
>        @@
>         test_run_rebase success ''
>         test_run_rebase success -m
>       @@ -603,31 +383,22 @@
>        +	git commit -m "Five letters ought to be enough for anybody"
>        +'
>        +
>       -+test_expect_success 'rebase --merge --empty=drop' '
>       ++test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
>       ++	test_when_finished "git rebase --abort" &&
>        +	git checkout -B testing localmods &&
>       -+	git rebase --merge --empty=drop upstream &&
>       -+
>       -+	test_write_lines C B A >expect &&
>       -+	git log --format=%s >actual &&
>       -+	test_cmp expect actual
>       -+'
>       ++	# rebase (--am) should not drop commits that start empty
>       ++	git rebase upstream &&
>        +
>       -+test_expect_success 'rebase --merge --empty=keep' '
>       -+	git checkout -B testing localmods &&
>       -+	git rebase --merge --empty=keep upstream &&
>       -+
>       -+	test_write_lines D C2 C B A >expect &&
>       ++	test_write_lines D C B A >expect &&
>        +	git log --format=%s >actual &&
>        +	test_cmp expect actual
>        +'
>        +
>       -+test_expect_success 'rebase --merge --empty=ask' '
>       ++test_expect_failure 'rebase --merge with a variety of empty commits' '
>       ++	test_when_finished "git rebase --abort" &&
>        +	git checkout -B testing localmods &&
>       -+	test_must_fail git rebase --merge --empty=ask upstream &&
>       -+
>       -+	test_must_fail git rebase --skip &&
>       -+	git commit --allow-empty &&
>       -+	git rebase --continue &&
>       ++	# rebase --merge should not halt on the commit that becomes empty
>       ++	git rebase --merge upstream &&
>        +
>        +	test_write_lines D C B A >expect &&
>        +	git log --format=%s >actual &&
>       @@ -636,25 +407,17 @@
>        +
>        +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>        +
>       -+test_expect_success 'rebase --interactive --empty=drop' '
>       ++test_expect_success 'rebase --interactive with a variety of empty commits' '
>        +	git checkout -B testing localmods &&
>       -+	git rebase --interactive --empty=drop upstream &&
>       ++	test_must_fail git rebase --interactive upstream &&
>        +
>       -+	test_write_lines C B A >expect &&
>       -+	git log --format=%s >actual &&
>       -+	test_cmp expect actual
>       -+'
>       -+
>       -+test_expect_success 'rebase --interactive --empty=keep' '
>       -+	git checkout -B testing localmods &&
>       -+	git rebase --interactive --empty=keep upstream &&
>       ++	git rebase --skip &&
>        +
>       -+	test_write_lines D C2 C B A >expect &&
>       ++	test_write_lines D C B A >expect &&
>        +	git log --format=%s >actual &&
>        +	test_cmp expect actual
>        +'
>        +
>       -+
>        +test_done
>        
>         diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
>       @@ -665,32 +428,28 @@
>         '
>         
>        -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
>       -+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
>       ++test_expect_success 'Rebase -Xsubtree --onto commit' '
>         	reset_rebase &&
>         	git checkout -b rebase-onto to-rebase &&
>        -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
>       -+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
>       ++	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
>         	: first pick results in no changes &&
>        -	git rebase --continue &&
>       -+	test_must_fail git rebase --skip &&
>       -+	: last pick was an empty commit that has no changes, but we want to keep it &&
>       -+	git commit --allow-empty &&
>       ++	git rebase --skip &&
>         	verbose test "$(commit_message HEAD~2)" = "master4" &&
>         	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>         	verbose test "$(commit_message HEAD)" = "Empty commit"
>         '
>         
>        -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
>       -+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
>       ++test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
>         	reset_rebase &&
>         	git checkout -b rebase-merges-onto to-rebase &&
>        -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
>       -+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
>       ++	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
>         	: first pick results in no changes &&
>        -	git rebase --continue &&
>       -+	test_must_fail git rebase --skip &&
>       -+	: last pick was an empty commit that has no changes, but we want to keep it &&
>       -+	git commit --allow-empty &&
>       ++	git rebase --skip &&
>         	verbose test "$(commit_message HEAD~2)" = "master4" &&
>         	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>         	verbose test "$(commit_message HEAD)" = "Empty commit"
>    -:  ---------- >  4:  c9542a2abe rebase (interactive-backend): fix handling of commits that become empty
>    2:  bd3c5ec155 =  5:  9f66229d5c t3406: simplify an already simple test
>    3:  49388b79fd =  6:  8d731fa39c rebase, sequencer: remove the broken GIT_QUIET handling
>    4:  478479358f !  7:  b6b6597eef rebase: make sure to pass along the quiet flag to the sequencer
>       @@ -8,13 +8,13 @@
>         --- a/builtin/rebase.c
>         +++ b/builtin/rebase.c
>        @@
>       + 	replay.allow_empty_message = opts->allow_empty_message;
>       + 	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
>         	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
>       - 	replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK &&
>       - 					 !(opts->flags & REBASE_INTERACTIVE_EXPLICIT));
>        +	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
>         	replay.verbose = opts->flags & REBASE_VERBOSE;
>         	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
>       - 	replay.committer_date_is_author_date =
>       + 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
>        @@
>         			 N_("allow pre-rebase hook to run")),
>         		OPT_NEGBIT('q', "quiet", &options.flags,
>    5:  ee26f5a161 =  8:  0acefa988b rebase: fix handling of restrict_revision
>    6:  34a69def33 =  9:  8c5b5b5133 t3432: make these tests work with either am or merge backends
>    7:  f2c92853b4 ! 10:  b8c087d6fb rebase: allow more types of rebases to fast-forward
>       @@ -35,8 +35,8 @@
>         		OPT_STRING(0, "onto", &options.onto_name,
>         			   N_("revision"),
>        @@
>       - 	    options.ignore_date)
>       - 		options.flags |= REBASE_FORCE;
>       + 		    state_dir_base, cmd_live_rebase, buf.buf);
>       + 	}
>         
>        +	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
>        +	    (action != ACTION_NONE) ||
>       @@ -47,7 +47,9 @@
>        +
>         	for (i = 0; i < options.git_am_opts.argc; i++) {
>         		const char *option = options.git_am_opts.argv[i], *p;
>       - 		if (!strcmp(option, "--whitespace=fix") ||
>       + 		if (!strcmp(option, "--committer-date-is-author-date") ||
>       + 		    !strcmp(option, "--ignore-date") ||
>       + 		    !strcmp(option, "--whitespace=fix") ||
>         		    !strcmp(option, "--whitespace=strip"))
>        -			options.flags |= REBASE_FORCE;
>        +			allow_preemptive_ff = 0;
>    8:  b307340f7c ! 11:  b50a1741e0 git-rebase.txt: add more details about behavioral differences of backends
>       @@ -8,23 +8,24 @@
>         --- a/Documentation/git-rebase.txt
>         +++ b/Documentation/git-rebase.txt
>        @@
>       - with `--keep-base` in order to drop those commits from your branch.
>         
>         --ignore-whitespace::
>       --	Behaves differently depending on which backend is selected.
>       --+
>       --'am' backend: When applying a patch, ignore changes in whitespace in
>       --context lines if necessary.
>       --+
>       --'interactive' backend: Treat lines with only whitespace changes as
>       --unchanged for the sake of a three-way merge.
>       -+	Ignore whitespace-only changes in the commits being rebased,
>       -+	which may avoid "unnecessary" conflicts.  (Both backends
>       -+	currently have differing edgecase bugs with this option; see
>       -+	BEHAVIORAL DIFFERENCES.)
>       -
>         --whitespace=<option>::
>       - 	This flag is passed to the 'git apply' program
>       +-	These flag are passed to the 'git apply' program
>       ++	These flags are passed to the 'git apply' program
>       + 	(see linkgit:git-apply[1]) that applies the patch.
>       + +
>       + See also INCOMPATIBLE OPTIONS below.
>       +@@
>       +
>       +  * --committer-date-is-author-date
>       +  * --ignore-date
>       +- * --whitespace
>       +  * --ignore-whitespace
>       ++ * --whitespace
>       +  * -C
>       +
>       + are incompatible with the following options:
>        @@
>         Directory rename detection
>         ~~~~~~~~~~~~~~~~~~~~~~~~~~
>       @@ -82,24 +83,14 @@
>        +The interactive backend works with the full commits on both sides of
>        +history and thus has no such limitations.
>        +
>       -+--ignore-whitespace
>       -+~~~~~~~~~~~~~~~~~~~
>       ++Hooks
>       ++~~~~~
>        +
>       -+The --ignore-whitespace option is supposed to ignore whitespace-only
>       -+changes if it allows the code to merge cleanly.  Unfortunately, the
>       -+different backends implement this differently, and both have different
>       -+edge case bugs.
>       -++
>       -+'am' backend: When applying a patch, ignore changes in whitespace in
>       -+context lines if necessary.  (Which implies that if the whitespace
>       -+change was not in the context lines but on a line with a real change,
>       -+then the rebase will still fail with "unnecessary" content conflicts.)
>       -++
>       -+'interactive' backend: Treat lines with only whitespace changes as
>       -+unchanged for the sake of a three-way merge.  This means that if one
>       -+side made no changes and the commits being rebased had whitespace-only
>       -+changes, those whitespaces fixups will be discarded despite the fact
>       -+that they present no content conflict.
>       ++The am backend has not traditionally called the post-commit hook,
>       ++while the merge/interactive backend has.  However, this was by
>       ++accident of implementation rather than by design.  Both backends
>       ++should have the same behavior, though it is not clear which one is
>       ++correct.
>        +
>        +Miscellaneous differences
>        +~~~~~~~~~~~~~~~~~~~~~~~~~
>       @@ -123,19 +114,3 @@
>         
>         include::merge-strategies.txt[]
>         
>       -
>       - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
>       - --- a/t/t3433-rebase-options-compatibility.sh
>       - +++ b/t/t3433-rebase-options-compatibility.sh
>       -@@
>       - GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
>       - export GIT_AUTHOR_DATE
>       -
>       --# This is a special case in which both am and interactive backends
>       --# provide the same output. It was done intentionally because
>       --# both the backends fall short of optimal behaviour.
>       -+# This is a common case in which both am and interactive backends
>       -+# provide the same output with --ignore-whitespace.
>       - test_expect_success 'setup' '
>       - 	git checkout -b topic &&
>       - 	q_to_tab >file <<-\EOF &&
>    9:  7c3f2e07f3 = 12:  58e6e4ffb3 rebase: move incompatibility checks between backend options a bit earlier
>   10:  1df11f0b51 ! 13:  5478c730ac rebase: add an --am option
>       @@ -24,8 +24,8 @@
>        +See also INCOMPATIBLE OPTIONS below.
>        +
>         --empty={drop,keep,ask}::
>       - 	How to handle commits that become empty (because they contain a
>       - 	subset of already upstream changes) or start empty.  With drop
>       + 	How to handle commits that are not empty to start and are not
>       + 	clean cherry-picks of any upstream commit, but which become
>        @@
>         	Ensure at least <n> lines of surrounding context match before
>         	and after each change.  When fewer lines of surrounding
>       @@ -37,7 +37,7 @@
>         
>        @@
>         --whitespace=<option>::
>       - 	This flag is passed to the 'git apply' program
>       + 	These flags are passed to the 'git apply' program
>         	(see linkgit:git-apply[1]) that applies the patch.
>        +	Implies --am.
>         +
>       @@ -48,9 +48,9 @@
>         The following options:
>         
>        + * --am
>       -  * --whitespace
>       -  * -C
>       -
>       +  * --committer-date-is-author-date
>       +  * --ignore-date
>       +  * --ignore-whitespace
>        
>         diff --git a/builtin/rebase.c b/builtin/rebase.c
>         --- a/builtin/rebase.c
>   11:  ff43593211 = 14:  db5e29bd81 git-prompt: change the prompt for interactive-based rebases
>    -:  ---------- > 15:  413e190ac9 rebase: drop '-i' from the reflog for interactive-based rebases
>   12:  99388f24e5 ! 16:  170be283a8 rebase tests: mark tests specific to the am-backend with --am
>       @@ -228,40 +228,6 @@
>         	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
>         }
>        
>       - diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
>       - --- a/t/t3433-rebase-options-compatibility.sh
>       - +++ b/t/t3433-rebase-options-compatibility.sh
>       -@@
>       - 	new line 2
>       - 	line 3
>       - 	EOF
>       --	test_must_fail git rebase main side &&
>       -+	test_must_fail git rebase --am main side &&
>       - 	git rebase --abort &&
>       --	git rebase --ignore-whitespace main side &&
>       -+	git rebase --am --ignore-whitespace main side &&
>       - 	test_cmp expect file
>       - '
>       -
>       -@@
>       -
>       - test_expect_success '--committer-date-is-author-date works with am backend' '
>       - 	git commit --amend &&
>       --	git rebase --committer-date-is-author-date HEAD^ &&
>       -+	git rebase --am --committer-date-is-author-date HEAD^ &&
>       - 	git show HEAD --pretty="format:%ai" >authortime &&
>       - 	git show HEAD --pretty="format:%ci" >committertime &&
>       - 	test_cmp authortime committertime
>       -@@
>       - # sets to +0530.
>       - test_expect_success '--ignore-date works with am backend' '
>       - 	git commit --amend --date="$GIT_AUTHOR_DATE" &&
>       --	git rebase --ignore-date HEAD^ &&
>       -+	git rebase --am --ignore-date HEAD^ &&
>       - 	git show HEAD --pretty="format:%ai" >authortime &&
>       - 	grep "+0000" authortime
>       - '
>       -
>         diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
>         --- a/t/t5407-post-rewrite-hook.sh
>         +++ b/t/t5407-post-rewrite-hook.sh
>   13:  c2ba6317bf = 17:  1e3d4066c4 rebase tests: repeat some tests using the merge backend instead of am
>   14:  8bec6df51a = 18:  9b4ac83d2d rebase: make the backend configurable via config setting
>   15:  044853fd61 = 19:  859a4a94d7 rebase: change the default backend from "am" to "merge"
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 00/19] rebase: make the default backend configurable
  2020-01-17 16:58       ` [PATCH v4 00/19] rebase: make the default backend configurable Phillip Wood
@ 2020-02-05 21:06         ` Junio C Hamano
  2020-02-05 22:38           ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-02-05 21:06 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, git, Johannes.Schindelin,
	phillip.wood, liu.denton, plroskin, alban.gruin, szeder.dev,
	jrnieder, emilyshaffer, Elijah Newren

Phillip Wood <phillip.wood123@gmail.com> writes:

>> Changes possibly missing from this version, for discussion:
>>
>>   * I did not remove the --am option as suggested by Phillip, since Junio and
>>     Phillip were still discussing whether it is wanted/needed.
>>   * I did not address the last two items Jonathan brought up as I 
>>     couldn't find enough information to reproduce or understand the problems.
>
> I think I've got a fix for the failure with
> --committer-date-is-author-date that Jonathan reported which I'll post
> next week - the bug was not in this series, it was just exposed by
> it. I'll try and read through this series next week as well.
>
> Best Wishes

Thanks.

As to the "--am" option, I do not care too deeply about it anymore,
so if there is nothing else that we need to further polish, should
we move this forward to 'next' soonish?

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 00/19] rebase: make the default backend configurable
  2020-02-05 21:06         ` Junio C Hamano
@ 2020-02-05 22:38           ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-02-05 22:38 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Phillip Wood, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder, Emily Shaffer

On Wed, Feb 5, 2020 at 1:07 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> >> Changes possibly missing from this version, for discussion:
> >>
> >>   * I did not remove the --am option as suggested by Phillip, since Junio and
> >>     Phillip were still discussing whether it is wanted/needed.
> >>   * I did not address the last two items Jonathan brought up as I
> >>     couldn't find enough information to reproduce or understand the problems.
> >
> > I think I've got a fix for the failure with
> > --committer-date-is-author-date that Jonathan reported which I'll post
> > next week - the bug was not in this series, it was just exposed by
> > it. I'll try and read through this series next week as well.
> >
> > Best Wishes
>
> Thanks.
>
> As to the "--am" option, I do not care too deeply about it anymore,
> so if there is nothing else that we need to further polish, should
> we move this forward to 'next' soonish?

I was hoping to hear back from Phillip, as he always provides great
comments.  I believe I've addressed all his comments up through v3,
but he hasn't had time in the last 2.5 weeks to review v4.

There was also an issue surrounding post-commit hooks that I think
Jonathan and/or Emily were looking in to.  I haven't heard from them
in a while, but I didn't think the issue was a blocker either.  If it
is, I can try to help push something along.

So, the current state is that there's nothing left for me to polish
that I know of.  If others know of things I've missed or want to
review v4 and point out changes I should make, I'm happy to make them.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message
  2020-01-16  6:14       ` [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
@ 2020-02-09 15:59         ` Phillip Wood
  2020-02-13 18:35           ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-02-09 15:59 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

Hi Elijah

On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> Commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the
> default", 2018-06-27) made --allow-empty-message the default and thus
> turned --allow-empty-message into a no-op but did not update the
> documentation to reflect this.  Update the documentation now, and hide
> the option from the normal -h output since it is not useful.
> 
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>   Documentation/git-rebase.txt |  7 ++++---
>   builtin/rebase.c             | 12 +++++++-----
>   2 files changed, 11 insertions(+), 8 deletions(-)
> 
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index 0c4f038dd6..c83be7ffc2 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.
>   See also INCOMPATIBLE OPTIONS below.
>   
>   --allow-empty-message::
> -	By default, rebasing commits with an empty message will fail.
> -	This option overrides that behavior, allowing commits with empty
> -	messages to be rebased.
> +	No-op.  Rebasing commits with an empty message used to fail
> +	and this option would override that behavior, allowing commits
> +	with empty messages to be rebased.  Now commits with an empty
> +	message do not cause rebasing to halt.

Do we want to mention that the user can pass --no-allow-empty-message to 
override this?

Best Wishes

Phillip

>   +
>   See also INCOMPATIBLE OPTIONS below.
>   
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 8081741f8a..faa4e0d406 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
>   		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
>   			   REBASE_FORCE),
>   		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
> -		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
> -			 N_("allow commits with empty messages")),
> +		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
> +			   N_("allow commits with empty messages"),
> +			   PARSE_OPT_HIDDEN),
>   		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
>   		OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
>   			 N_("keep original branch points of cousins")),
> @@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
>   				N_("add exec lines after each commit of the "
>   				   "editable list")),
> -		OPT_BOOL(0, "allow-empty-message",
> -			 &options.allow_empty_message,
> -			 N_("allow rebasing commits with empty messages")),
> +		OPT_BOOL_F(0, "allow-empty-message",
> +			   &options.allow_empty_message,
> +			   N_("allow rebasing commits with empty messages"),
> +			   PARSE_OPT_HIDDEN),
>   		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
>   			N_("mode"),
>   			N_("try to rebase merges instead of skipping them"),
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default
  2020-01-16  6:14       ` [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
@ 2020-02-09 15:59         ` Phillip Wood
  2020-02-13 18:52           ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-02-09 15:59 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

Hi Elijah

On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> Different rebase backends have different treatment for commits which
> start empty (i.e. have no changes relative to their parent), and the
> --keep-empty option was added at some point to allow adjusting behavior
> for the interactive backend.

--keep-empty was introduced by 90e1818f9a ("git-rebase: add keep_empty 
flag", 2012-04-20) and was respected for am based rebases as well 
(although the implementation was broken if there were any conflicts)

> The handling of commits which start empty
> is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
> --allow-empty-message the default, 2018-06-27), which pointed out that
> the behavior for various backends is often more happenstance than
> design.  The specific change made in that commit is actually quite
> relevant as well and much of the logic there directly applies here.
> 
> It makes a lot of sense in 'git commit' to error out on the creation of
> empty commits, unless an override flag is provided.  However, once
> someone determines that there is a rare case that merits using the
> manual override to create such a commit, it is somewhere between
> annoying and harmful to have to take extra steps to keep such
> intentional commits around.  Granted, empty commits are quite rare,
> which is why handling of them doesn't get considered much and folks tend
> to defer to existing (accidental) behavior and assume there was a reason
> for it, leading them to just add flags (--keep-empty in this case) that
> allow them to override the bad defaults.  Fix the interactive backend so
> that --keep-empty is the default, much like we did with
> --allow-empty-message.  The am backend should also be fixed to have
> --keep-empty semantics for commits that start empty, but that is not
> included in this patch other than a testcase documenting the failure.

The detailed commit message is great, however I think it would be good 
to point out that this changes things so that --no-keep-empty is parsed 
but does nothing - it is no longer possible to get that behavior. I'm 
not  entirely sure about making --keep-empty hidden given that the am 
backend has a different behavior without it but as we're changing the 
default backend it probably does not matter and it looks like 
--keep-empty combined with an am specific option will still error out 
which is good.

Overall I like this change and it simplifies the implementation which is 
nice. Hopefully no one was relying on --no-keep-empty

> Note that there was one test in t3421 which appears to have been written
> expecting --keep-empty to not be the default as correct behavior.  This
> test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
> empty commits", 2013-06-06), which was part of a series focusing on
> rebase topology and which had an interesting original cover letter at
> https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
> which noted
>      Your input especially appreciated on whether you agree with the
>      intent of the test cases.
> and then went into a long example about how one of the many tests added
> had several questions about whether it was correct.  As such, I believe
> most the tests in that series were about testing rebase topology with as
> many different flags as possible and were not trying to state in general
> how those flags should behave otherwise.
> 
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>   Documentation/git-rebase.txt      | 24 ++++++-----
>   builtin/rebase.c                  | 28 ++++++++----
>   rebase-interactive.c              |  7 +--
>   rebase-interactive.h              |  2 +-
>   sequencer.c                       | 20 +++------
>   sequencer.h                       |  2 +-
>   t/t3421-rebase-topology-linear.sh | 16 +++----
>   t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++
>   t/t3427-rebase-subtree.sh         | 12 +++---
>   9 files changed, 128 insertions(+), 55 deletions(-)
>   create mode 100755 t/t3424-rebase-empty.sh
> 
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index c83be7ffc2..1d19542d79 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.
>   	unchanged as a result.
>   
>   --keep-empty::
> -	Keep the commits that do not change anything from its
> -	parents in the result.
> +	No-op.  Rebasing commits that started empty (had no change
> +	relative to their parent) used to fail and this option would
> +	override that behavior, allowing commits with empty changes to
> +	be rebased.  Now commits with no changes do not cause rebasing
> +	to halt.

Maybe mention that --no-keep-empty is no longer respected?

> -See also INCOMPATIBLE OPTIONS below.
> +See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
>   
>   --allow-empty-message::
>   	No-op.  Rebasing commits with an empty message used to fail
> @@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.
>   Empty commits
>   ~~~~~~~~~~~~~
>   
> -The am backend drops any "empty" commits, regardless of whether the
> -commit started empty (had no changes relative to its parent to
> -start with) or ended empty (all changes were already applied
> -upstream in other commits).
> +The am backend unfortunately drops intentionally empty commits, i.e.
> +commits that started empty, though these are rare in practice.  It
> +also drops commits that become empty and has no option for controlling
> +this behavior.
>   
> -The interactive backend drops commits by default that
> -started empty and halts if it hits a commit that ended up empty.
> -The `--keep-empty` option exists for the interactive backend to allow
> -it to keep commits that started empty.
> +The interactive backend keeps intentionally empty commits.
> +Unfortunately, it always halts whenever it runs across a commit that
> +becomes empty, even when the rebase is not explicitly interactive.
>   
>   Directory rename detection
>   ~~~~~~~~~~~~~~~~~~~~~~~~~~
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index faa4e0d406..537b3241ce 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -77,7 +77,6 @@ struct rebase_options {
>   	const char *action;
>   	int signoff;
>   	int allow_rerere_autoupdate;
> -	int keep_empty;
>   	int autosquash;
>   	char *gpg_sign_opt;
>   	int autostash;
> @@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
>   
>   	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
>   
> -	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
>   	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
>   	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
>   	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
> @@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,
>   	return ret;
>   }
>   
> +static int parse_opt_keep_empty(const struct option *opt, const char *arg,
> +				int unset)
> +{
> +	struct rebase_options *opts = opt->value;
> +
> +	BUG_ON_OPT_ARG(arg);
> +
> +	opts->type = REBASE_INTERACTIVE;
> +	return 0;
> +}

This doesn't seem to handle --no-keep-empty (but looking through the 
patch that seems to be intentional)

>   static const char * const builtin_rebase_interactive_usage[] = {
>   	N_("git rebase--interactive [<options>]"),
>   	NULL
> @@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
>   	struct option options[] = {
>   		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
>   			   REBASE_FORCE),
> -		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
> +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> +			N_("(DEPRECATED) keep empty commits"),
> +			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> +			parse_opt_keep_empty },
>   		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
>   			   N_("allow commits with empty messages"),
>   			   PARSE_OPT_HIDDEN),
> @@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
>   		opts->allow_rerere_autoupdate ?
>   			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
>   			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
> -	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
>   	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
>   	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
>   	add_var(&script_snippet, "cmd", opts->cmd);
> @@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   				 "ignoring them"),
>   			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>   		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
> -		OPT_BOOL('k', "keep-empty", &options.keep_empty,
> -			 N_("preserve empty commits during rebase")),
> +		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> +			N_("(DEPRECATED) keep empty commits"),
> +			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> +			parse_opt_keep_empty },
>   		OPT_BOOL(0, "autosquash", &options.autosquash,
>   			 N_("move commits that begin with "
>   			    "squash!/fixup! under -i")),
> @@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	if (!(options.flags & REBASE_NO_QUIET))
>   		argv_array_push(&options.git_am_opts, "-q");
>   
> -	if (options.keep_empty)
> -		imply_interactive(&options, "--keep-empty");
> -
>   	if (gpg_sign) {
>   		free(options.gpg_sign_opt);
>   		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
> diff --git a/rebase-interactive.c b/rebase-interactive.c
> index aa18ae82b7..7e7b4061bf 100644
> --- a/rebase-interactive.c
> +++ b/rebase-interactive.c
> @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
>   	return MISSING_COMMIT_CHECK_IGNORE;
>   }
>   
> -void append_todo_help(unsigned keep_empty, int command_count,
> +void append_todo_help(int command_count,
>   		      const char *shortrevisions, const char *shortonto,
>   		      struct strbuf *buf)
>   {
> @@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,
>   			"the rebase will be aborted.\n\n");
>   
>   	strbuf_add_commented_lines(buf, msg, strlen(msg));
> -
> -	if (!keep_empty) {
> -		msg = _("Note that empty commits are commented out");
> -		strbuf_add_commented_lines(buf, msg, strlen(msg));
> -	}
>   }
>   
>   int edit_todo_list(struct repository *r, struct todo_list *todo_list,
> diff --git a/rebase-interactive.h b/rebase-interactive.h
> index 44dbb06311..05354ca341 100644
> --- a/rebase-interactive.h
> +++ b/rebase-interactive.h
> @@ -5,7 +5,7 @@ struct strbuf;
>   struct repository;
>   struct todo_list;
>   
> -void append_todo_help(unsigned keep_empty, int command_count,
> +void append_todo_help(int command_count,
>   		      const char *shortrevisions, const char *shortonto,
>   		      struct strbuf *buf);
>   int edit_todo_list(struct repository *r, struct todo_list *todo_list,
> diff --git a/sequencer.c b/sequencer.c
> index b9dbf1adb0..c21fc202b1 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,
>   		       struct replay_opts *opts,
>   		       struct commit *commit)
>   {
> -	int index_unchanged, empty_commit;
> +	int index_unchanged, originally_empty;
>   
>   	/*
>   	 * Three cases:
> @@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,
>   	if (opts->keep_redundant_commits)
>   		return 1;
>   
> -	empty_commit = is_original_commit_empty(commit);
> -	if (empty_commit < 0)
> -		return empty_commit;
> -	if (!empty_commit)
> +	originally_empty = is_original_commit_empty(commit);
> +	if (originally_empty < 0)
> +		return originally_empty;
> +	if (!originally_empty)
>   		return 0;
>   	else
>   		return 1;
> @@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
>   				   struct rev_info *revs, struct strbuf *out,
>   				   unsigned flags)
>   {
> -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
>   	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
>   	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
>   	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
> @@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
>   		if (!to_merge) {
>   			/* non-merge commit: easy case */
>   			strbuf_reset(&buf);
> -			if (!keep_empty && is_empty)
> -				strbuf_addf(&buf, "%c ", comment_line_char);
>   			strbuf_addf(&buf, "%s %s %s", cmd_pick,
>   				    oid_to_hex(&commit->object.oid),
>   				    oneline.buf);
> @@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
>   	struct pretty_print_context pp = {0};
>   	struct rev_info revs;
>   	struct commit *commit;
> -	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
>   	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
>   	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
>   
> @@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
>   		return make_script_with_merges(&pp, &revs, out, flags);
>   
>   	while ((commit = get_revision(&revs))) {
> -		int is_empty  = is_original_commit_empty(commit);
> +		int is_empty = is_original_commit_empty(commit);
>   
>   		if (!is_empty && (commit->object.flags & PATCHSAME))
>   			continue;
> -		if (!keep_empty && is_empty)
> -			strbuf_addf(out, "%c ", comment_line_char);
>   		strbuf_addf(out, "%s %s ", insn,
>   			    oid_to_hex(&commit->object.oid));
>   		pretty_print_commit(&pp, commit, out);
> @@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
>   
>   	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
>   	if (flags & TODO_LIST_APPEND_TODO_HELP)
> -		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
> +		append_todo_help(count_commands(todo_list),
>   				 shortrevisions, shortonto, &buf);
>   
>   	res = write_message(buf.buf, buf.len, file, 0);
> diff --git a/sequencer.h b/sequencer.h
> index 9f9ae291e3..c165e0ff25 100644
> --- a/sequencer.h
> +++ b/sequencer.h
> @@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
>   int sequencer_skip(struct repository *repo, struct replay_opts *opts);
>   int sequencer_remove_state(struct replay_opts *opts);
>   
> -#define TODO_LIST_KEEP_EMPTY (1U << 0)
> +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
>   #define TODO_LIST_SHORTEN_IDS (1U << 1)
>   #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
>   #define TODO_LIST_REBASE_MERGES (1U << 3)
> diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
> index 325072b0a3..57334dca7e 100755
> --- a/t/t3421-rebase-topology-linear.sh
> +++ b/t/t3421-rebase-topology-linear.sh
> @@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '
>   test_run_rebase () {
>   	result=$1
>   	shift
> -	test_expect_$result "rebase $* drops empty commit" "
> +	test_expect_$result "rebase $* keeps begin-empty commits" "
>   		reset_rebase &&
> -		git rebase $* c l &&
> -		test_cmp_rev c HEAD~2 &&
> -		test_linear_range 'd l' c..
> +		git rebase $* j l &&
> +		test_cmp_rev c HEAD~4 &&
> +		test_linear_range 'j d k l' c..
>   	"
>   }
> -test_run_rebase success ''
> +test_run_rebase failure ''
>   test_run_rebase success -m
>   test_run_rebase success -i
> -test_have_prereq !REBASE_P || test_run_rebase success -p
> +test_have_prereq !REBASE_P || test_run_rebase failure -p
>   
>   test_run_rebase () {
>   	result=$1
> @@ -230,7 +230,7 @@ test_run_rebase () {
>   test_run_rebase success ''
>   test_run_rebase success -m
>   test_run_rebase success -i
> -test_have_prereq !REBASE_P || test_run_rebase failure -p
> +test_have_prereq !REBASE_P || test_run_rebase success -p
>   
>   test_run_rebase () {
>   	result=$1
> @@ -245,7 +245,7 @@ test_run_rebase () {
>   test_run_rebase success ''
>   test_run_rebase success -m
>   test_run_rebase success -i
> -test_have_prereq !REBASE_P || test_run_rebase failure -p
> +test_have_prereq !REBASE_P || test_run_rebase success -p
>   test_run_rebase success --rebase-merges
>   
>   #       m
> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
> new file mode 100755
> index 0000000000..22d97e143b
> --- /dev/null
> +++ b/t/t3424-rebase-empty.sh
> @@ -0,0 +1,72 @@
> +#!/bin/sh
> +
> +test_description='git rebase of commits that start or become empty'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup test repository' '
> +	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
> +	test_write_lines A B C D E F G H I J >letters &&
> +	git add numbers letters &&
> +	git commit -m A &&
> +
> +	git branch upstream &&
> +	git branch localmods &&
> +
> +	git checkout upstream &&
> +	test_write_lines A B C D E >letters &&
> +	git add letters &&
> +	git commit -m B &&
> +
> +	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
> +	git add numbers &&
> +	git commit -m C &&
> +
> +	git checkout localmods &&
> +	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
> +	git add numbers &&
> +	git commit -m C2 &&
> +
> +	git commit --allow-empty -m D &&
> +
> +	test_write_lines A B C D E >letters &&
> +	git add letters &&
> +	git commit -m "Five letters ought to be enough for anybody"
> +'
> +
> +test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
> +	test_when_finished "git rebase --abort" &&
> +	git checkout -B testing localmods &&
> +	# rebase (--am) should not drop commits that start empty
> +	git rebase upstream &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_failure 'rebase --merge with a variety of empty commits' '
> +	test_when_finished "git rebase --abort" &&
> +	git checkout -B testing localmods &&
> +	# rebase --merge should not halt on the commit that becomes empty
> +	git rebase --merge upstream &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR

This doesn't do any harm but I'm not sure it's needed

Best Wishes

Phillip

> +test_expect_success 'rebase --interactive with a variety of empty commits' '
> +	git checkout -B testing localmods &&
> +	test_must_fail git rebase --interactive upstream &&
> +
> +	git rebase --skip &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_done
> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
> index bec48e6a1f..8dceef61cf 100755
> --- a/t/t3427-rebase-subtree.sh
> +++ b/t/t3427-rebase-subtree.sh
> @@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
>   	verbose test "$(commit_message HEAD)" = "Empty commit"
>   '
>   
> -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --onto commit' '
>   	reset_rebase &&
>   	git checkout -b rebase-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
>   	: first pick results in no changes &&
> -	git rebase --continue &&
> +	git rebase --skip &&
>   	verbose test "$(commit_message HEAD~2)" = "master4" &&
>   	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>   	verbose test "$(commit_message HEAD)" = "Empty commit"
>   '
>   
> -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
>   	reset_rebase &&
>   	git checkout -b rebase-merges-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
>   	: first pick results in no changes &&
> -	git rebase --continue &&
> +	git rebase --skip &&
>   	verbose test "$(commit_message HEAD~2)" = "master4" &&
>   	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
>   	verbose test "$(commit_message HEAD)" = "Empty commit"
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
  2020-01-16  6:14       ` [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
@ 2020-02-10 14:27         ` Phillip Wood
  2020-02-13 18:54           ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Phillip Wood @ 2020-02-10 14:27 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

Hi Elijah

On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> As established in the previous commit and commit b00bf1c9a8dd
> (git-rebase: make --allow-empty-message the default, 2018-06-27), the
> behavior for rebase with different backends in various edge or corner
> cases is often more happenstance than design.  This commit addresses
> another such corner case: commits which "become empty".
> 
> A careful reader may note that there are two types of commits which would
> become empty due to a rebase:
> 
>    * [clean cherry-pick] Commits which are clean cherry-picks of upstream
>      commits, as determined by `git log --cherry-mark ...`.  Re-applying
>      these commits would result in an empty set of changes and a
>      duplicative commit message; i.e. these are commits that have
>      "already been applied" upstream.
> 
>    * [become empty] Commits which are not empty to start, are not clean
>      cherry-picks of upstream commits, but which still become empty after
>      being rebased.  This happens e.g. when a commit has changes which
>      are a strict subset of the changes in an upstream commit, or when
>      the changes of a commit can be found spread across or among several
>      upstream commits.
> 
> Clearly, in both cases the changes in the commit in question are found
> upstream already, but the commit message may not be in the latter case.
> 
> When cherry-mark can determine a commit is already upstream, then
> because of how cherry-mark works this means the upstream commit message
> was about the *exact* same set of changes.  Thus, the commit messages
> can be assumed to be fully interchangeable (and are in fact likely to be
> completely identical).  As such, the clean cherry-pick case represents a
> case when there is no information to be gained by keeping the extra
> commit around.  All rebase types have always dropped these commits, and
> no one to my knowledge has ever requested that we do otherwise.
> 
> For many of the become empty cases (and likely even most), we will also
> be able to drop the commit without loss of information -- but this isn't
> quite always the case.  Since these commits represent cases that were
> not clean cherry-picks, there is no upstream commit message explaining
> the same set of changes.  Projects with good commit message hygiene will
> likely have the explanation from our commit message contained within or
> spread among the relevant upstream commits, but not all projects run
> that way.  As such, the commit message of the commit being rebased may
> have reasoning that suggests additional changes that should be made to
> adapt to the new base, or it may have information that someone wants to
> add as a note to another commit, or perhaps someone even wants to create
> an empty commit with the commit message as-is.
> 
> Junio commented on the "become-empty" types of commits as follows[1]:
> 
>      WRT a change that ends up being empty (as opposed to a change that
>      is empty from the beginning), I'd think that the current behaviour
>      is desireable one.  "am" based rebase is solely to transplant an
>      existing history and want to stop much less than "interactive" one
>      whose purpose is to polish a series before making it publishable,
>      and asking for confirmation ("this has become empty--do you want to
>      drop it?") is more appropriate from the workflow point of view.
> 
> [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
> 
> I would simply add that his arguments for "am"-based rebases actually
> apply to all non-explicitly-interactive rebases.  Also, since we are
> stating that different cases should have different defaults, it may be
> worth providing a flag to allow users to select which behavior they want
> for these commits.
> 
> Introduce a new command line flag for selecting the desired behavior:
>      --empty={drop,keep,ask}
> with the definitions:
>      drop: drop commits which become empty
>      keep: keep commits which become empty
>      ask:  provide the user a chance to interact and pick what to do with
>            commits which become empty on a case-by-case basis
> 
> In line with Junio's suggestion, if the --empty flag is not specified,
> pick defaults as follows:
>      explicitly interactive: ask
>      otherwise: drop

Looking at the implementation there is a third option - if `--exec` is 
given without `-i` then the default is "keep". I'm not sure if having 
different defaults is convenient or confusing but don't feel that 
strongly about it. I've got a few minor comments below (the mains ones 
are saying which commit has been dropped and testing the default 
behavior when --empty is not given) but basically I like the new patch. 
Thanks for working on this, the commit message does a good job of 
explaining the changes.

> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
>   Documentation/git-rebase.txt | 27 ++++++++++++++++---
>   builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++
>   sequencer.c                  | 48 +++++++++++++++++++++++++--------
>   sequencer.h                  |  1 +
>   t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----
>   t/t3427-rebase-subtree.sh    |  8 +++---
>   6 files changed, 161 insertions(+), 25 deletions(-)
> 
> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> index 1d19542d79..551a91d764 100644
> --- a/Documentation/git-rebase.txt
> +++ b/Documentation/git-rebase.txt
> @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.
>   	original branch. The index and working tree are also left
>   	unchanged as a result.
>   
> +--empty={drop,keep,ask}::
> +	How to handle commits that are not empty to start and are not
> +	clean cherry-picks of any upstream commit, but which become
> +	empty after rebasing (because they contain a subset of already
> +	upstream changes).  With drop (the default), commits that
> +	become empty are dropped.  With keep, such commits are kept.
> +	With ask (implied by --interactive), the rebase will halt when
> +	an empty commit is applied allowing you to choose whether to
> +	drop it, edit files more, or just commit the empty changes.

We should probably document the default for --exec without -i

> +Note that commits which start empty are kept, and commits which are
> +clean cherry-picks (as determined by `git log --cherry-mark ...`) are
> +always dropped.
> ++
> +See also INCOMPATIBLE OPTIONS below.
> +
>   --keep-empty::
>   	No-op.  Rebasing commits that started empty (had no change
>   	relative to their parent) used to fail and this option would
> @@ -561,6 +577,7 @@ are incompatible with the following options:
>    * --interactive
>    * --exec
>    * --keep-empty
> + * --empty=
>    * --edit-todo
>    * --root when used in combination with --onto
>   
> @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:
>    * --preserve-merges and --interactive
>    * --preserve-merges and --signoff
>    * --preserve-merges and --rebase-merges
> + * --preserve-merges and --empty=
>    * --keep-base and --onto
>    * --keep-base and --root
>   
> @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It
>   also drops commits that become empty and has no option for controlling
>   this behavior.
>   
> -The interactive backend keeps intentionally empty commits.
> -Unfortunately, it always halts whenever it runs across a commit that
> -becomes empty, even when the rebase is not explicitly interactive.
> +The interactive backend keeps intentionally empty commits.  Similar to
> +the am backend, by default the interactive backend drops commits that
> +become empty unless -i/--interactive is specified (in which case it
> +stops and asks the user what to do).  The interactive backend also has
> +an --empty={drop,keep,ask} option for changing the behavior of
> +handling commits that become empty.
>   
>   Directory rename detection
>   ~~~~~~~~~~~~~~~~~~~~~~~~~~
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 537b3241ce..c299869e7b 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -50,8 +50,16 @@ enum rebase_type {
>   	REBASE_PRESERVE_MERGES
>   };
>   
> +enum empty_type {
> +	EMPTY_UNSPECIFIED = -1,
> +	EMPTY_DROP,
> +	EMPTY_KEEP,
> +	EMPTY_ASK
> +};
> +
>   struct rebase_options {
>   	enum rebase_type type;
> +	enum empty_type empty;
>   	const char *state_dir;
>   	struct commit *upstream;
>   	const char *upstream_name;
> @@ -91,6 +99,7 @@ struct rebase_options {
>   
>   #define REBASE_OPTIONS_INIT {			  	\
>   		.type = REBASE_UNSPECIFIED,	  	\
> +		.empty = EMPTY_UNSPECIFIED,	  	\
>   		.flags = REBASE_NO_QUIET, 		\
>   		.git_am_opts = ARGV_ARRAY_INIT,		\
>   		.git_format_patch_opt = STRBUF_INIT	\
> @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>   		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
>   	replay.allow_empty = 1;
>   	replay.allow_empty_message = opts->allow_empty_message;
> +	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
> +	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
>   	replay.verbose = opts->flags & REBASE_VERBOSE;
>   	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
>   	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
>   
>   	BUG_ON_OPT_ARG(arg);
>   
> +	/*
> +	 * If we ever want to remap --keep-empty to --empty=keep, insert:
> +	 * 	opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
> +	 */
>   	opts->type = REBASE_INTERACTIVE;
>   	return 0;
>   }
> @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
>   	return 0;
>   }
>   
> +static enum empty_type parse_empty_value(const char *value)
> +{
> +	if (!strcasecmp(value, "drop"))
> +		return EMPTY_DROP;
> +	else if (!strcasecmp(value, "keep"))
> +		return EMPTY_KEEP;
> +	else if (!strcasecmp(value, "ask"))
> +		return EMPTY_ASK;
> +
> +	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
> +}
> +
> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
> +{
> +	struct rebase_options *options = opt->value;
> +	enum empty_type value = parse_empty_value(arg);
> +
> +	BUG_ON_OPT_NEG(unset);
> +
> +	options->empty = value;
> +	return 0;
> +}
> +
>   static void NORETURN error_on_missing_default_upstream(void)
>   {
>   	struct branch *current_branch = branch_get(NULL);
> @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   				 "ignoring them"),
>   			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>   		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
> +		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
> +			       N_("how to handle empty commits"),

Maybe we should say "how to handle commits that become empty" to 
distinguish them from commits that start empty which we always keep

> +			       PARSE_OPT_NONEG, parse_opt_empty),
>   		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>   			N_("(DEPRECATED) keep empty commits"),
>   			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   	if (!(options.flags & REBASE_NO_QUIET))
>   		argv_array_push(&options.git_am_opts, "-q");
>   
> +	if (options.empty != EMPTY_UNSPECIFIED)
> +		imply_interactive(&options, "--empty");
> +
>   	if (gpg_sign) {
>   		free(options.gpg_sign_opt);
>   		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
> @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>   		break;
>   	}
>   
> +	if (options.empty == EMPTY_UNSPECIFIED) {
> +		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
> +			options.empty = EMPTY_ASK;
> +		else if (exec.nr > 0)
> +			options.empty = EMPTY_KEEP;
> +		else
> +			options.empty = EMPTY_DROP;
> +	}
>   	if (reschedule_failed_exec > 0 && !is_interactive(&options))
>   		die(_("--reschedule-failed-exec requires "
>   		      "--exec or --interactive"));
> diff --git a/sequencer.c b/sequencer.c
> index c21fc202b1..354d0b5a38 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
>   static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
>   static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
>   static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
>   
>   static int git_sequencer_config(const char *k, const char *v, void *cb)
>   {
> @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)
>   }
>   
>   /*
> - * Do we run "git commit" with "--allow-empty"?
> + * Should empty commits be allowed?  Return status:
> + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
> + *     0: Halt on empty commit
> + *     1: Allow empty commit
> + *     2: Drop empty commit
>    */
>   static int allow_empty(struct repository *r,
>   		       struct replay_opts *opts,
> @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,
>   	int index_unchanged, originally_empty;
>   
>   	/*
> -	 * Three cases:
> +	 * Four cases:
>   	 *
>   	 * (1) we do not allow empty at all and error out.
>   	 *
> -	 * (2) we allow ones that were initially empty, but
> -	 * forbid the ones that become empty;
> +	 * (2) we allow ones that were initially empty, and
> +	 *     just drop the ones that become empty
>   	 *
> -	 * (3) we allow both.
> +	 * (3) we allow ones that were initially empty, but
> +	 *     halt for the ones that become empty;
> +	 *
> +	 * (4) we allow both.
>   	 */
>   	if (!opts->allow_empty)
>   		return 0; /* let "git commit" barf as necessary */
> @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,
>   	originally_empty = is_original_commit_empty(commit);
>   	if (originally_empty < 0)
>   		return originally_empty;
> -	if (!originally_empty)
> -		return 0;
> -	else
> +	if (originally_empty)
>   		return 1;
> +	else if (opts->drop_redundant_commits)
> +		return 2;
> +	else
> +		return 0;
>   }
>   
>   static struct {
> @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,
>   	char *author = NULL;
>   	struct commit_message msg = { NULL, NULL, NULL, NULL };
>   	struct strbuf msgbuf = STRBUF_INIT;
> -	int res, unborn = 0, reword = 0, allow;
> +	int res, unborn = 0, reword = 0, allow, drop_commit;
>   
>   	if (opts->no_commit) {
>   		/*
> @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,
>   		goto leave;
>   	}
>   
> +	drop_commit = 0;
>   	allow = allow_empty(r, opts, commit);
>   	if (allow < 0) {
>   		res = allow;
>   		goto leave;
> -	} else if (allow)
> +	} else if (allow == 1) {
>   		flags |= ALLOW_EMPTY;
> -	if (!opts->no_commit) {
> +	} else if (allow == 2) {
> +		drop_commit = 1;
> +		fprintf(stderr, _("No changes -- Patch already applied.\n"));

nit pick - usually messages start with a lowercase letter. Would it be 
helpful to explicitly state which commit is being dropped as well as 
why? Something like
   dropping <oid> <subject> - patch contents already upstream

> +	} // else allow == 0 and there's nothing special to do

We don't use // for comments

> +	if (!opts->no_commit && !drop_commit) {
>   		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
>   			res = do_commit(r, msg_file, author, opts, flags);
>   		else
> @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)
>   		if (file_exists(rebase_path_reschedule_failed_exec()))
>   			opts->reschedule_failed_exec = 1;
>   
> +		if (file_exists(rebase_path_drop_redundant_commits()))
> +			opts->drop_redundant_commits = 1;
> +
> +		if (file_exists(rebase_path_keep_redundant_commits()))
> +			opts->keep_redundant_commits = 1;
> +
>   		read_strategy_opts(opts, &buf);
>   		strbuf_release(&buf);
>   
> @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
>   		write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
>   	if (opts->signoff)
>   		write_file(rebase_path_signoff(), "--signoff\n");
> +	if (opts->drop_redundant_commits)
> +		write_file(rebase_path_drop_redundant_commits(), "%s", "");
> +	if (opts->keep_redundant_commits)
> +		write_file(rebase_path_keep_redundant_commits(), "%s", "");
>   	if (opts->reschedule_failed_exec)
>   		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
>   
> diff --git a/sequencer.h b/sequencer.h
> index c165e0ff25..3b0ab9141f 100644
> --- a/sequencer.h
> +++ b/sequencer.h
> @@ -39,6 +39,7 @@ struct replay_opts {
>   	int allow_rerere_auto;
>   	int allow_empty;
>   	int allow_empty_message;
> +	int drop_redundant_commits;
>   	int keep_redundant_commits;
>   	int verbose;
>   	int quiet;
> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
> index 22d97e143b..dcb4cb4751 100755
> --- a/t/t3424-rebase-empty.sh
> +++ b/t/t3424-rebase-empty.sh
> @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '
>   	git commit -m "Five letters ought to be enough for anybody"
>   '
>   
> -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
> +test_expect_failure 'rebase (am-backend)' '
>   	test_when_finished "git rebase --abort" &&
>   	git checkout -B testing localmods &&
>   	# rebase (--am) should not drop commits that start empty
> @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
>   	test_cmp expect actual
>   '
>   
> -test_expect_failure 'rebase --merge with a variety of empty commits' '
> -	test_when_finished "git rebase --abort" &&
> +test_expect_success 'rebase --merge --empty=drop' '
>   	git checkout -B testing localmods &&
> -	# rebase --merge should not halt on the commit that becomes empty
> -	git rebase --merge upstream &&
> +	git rebase --merge --empty=drop upstream &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --merge --empty=keep' '
> +	git checkout -B testing localmods &&
> +	git rebase --merge --empty=keep upstream &&
> +
> +	test_write_lines D C2 C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --merge --empty=ask' '
> +	git checkout -B testing localmods &&
> +	test_must_fail git rebase --merge --empty=ask upstream &&
> +
> +	git rebase --skip &&
>   
>   	test_write_lines D C B A >expect &&
>   	git log --format=%s >actual &&
> @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '
>   
>   GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>   
> -test_expect_success 'rebase --interactive with a variety of empty commits' '
> +test_expect_success 'rebase --interactive --empty=drop' '
> +	git checkout -B testing localmods &&
> +	git rebase --interactive --empty=drop upstream &&
> +
> +	test_write_lines D C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --interactive --empty=keep' '
> +	git checkout -B testing localmods &&
> +	git rebase --interactive --empty=keep upstream &&
> +
> +	test_write_lines D C2 C B A >expect &&
> +	git log --format=%s >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'rebase --interactive --empty=ask' '
>   	git checkout -B testing localmods &&
> -	test_must_fail git rebase --interactive upstream &&
> +	test_must_fail git rebase --interactive --empty=ask upstream &&
>   
>   	git rebase --skip &&

As the default if --empty is not given is supposed to vary depending on 
the other options given it would be good to test that I think

Best Wishes

Phillip

> diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
> index 8dceef61cf..79e43a370b 100755
> --- a/t/t3427-rebase-subtree.sh
> +++ b/t/t3427-rebase-subtree.sh
> @@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
>   	verbose test "$(commit_message HEAD)" = "Empty commit"
>   '
>   
> -test_expect_success 'Rebase -Xsubtree --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
>   	reset_rebase &&
>   	git checkout -b rebase-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
>   	: first pick results in no changes &&
>   	git rebase --skip &&
>   	verbose test "$(commit_message HEAD~2)" = "master4" &&
> @@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '
>   	verbose test "$(commit_message HEAD)" = "Empty commit"
>   '
>   
> -test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
> +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
>   	reset_rebase &&
>   	git checkout -b rebase-merges-onto to-rebase &&
> -	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
> +	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
>   	: first pick results in no changes &&
>   	git rebase --skip &&
>   	verbose test "$(commit_message HEAD~2)" = "master4" &&
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message
  2020-02-09 15:59         ` Phillip Wood
@ 2020-02-13 18:35           ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-02-13 18:35 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder, Emily Shaffer

Hi Phillip,

On Sun, Feb 9, 2020 at 7:59 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Elijah
>
...
> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> > index 0c4f038dd6..c83be7ffc2 100644
> > --- a/Documentation/git-rebase.txt
> > +++ b/Documentation/git-rebase.txt
> > @@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.
> >   See also INCOMPATIBLE OPTIONS below.
> >
> >   --allow-empty-message::
> > -     By default, rebasing commits with an empty message will fail.
> > -     This option overrides that behavior, allowing commits with empty
> > -     messages to be rebased.
> > +     No-op.  Rebasing commits with an empty message used to fail
> > +     and this option would override that behavior, allowing commits
> > +     with empty messages to be rebased.  Now commits with an empty
> > +     message do not cause rebasing to halt.
>
> Do we want to mention that the user can pass --no-allow-empty-message to
> override this?

Interesting point.  I could be persuaded towards that, but I'm leaning
against it based on the logic in b00bf1c9a8dd which this commit was
based upon.

However, even if we do want to mention --no-allow-empty-message, I'd
rather do that in a different series.  This patch is already slightly
tangential to the series, but it at least fixes the documentation for
a previously-documented option -- an option that happens to be similar
in spirit to the ones being touched by the series.  Adding
documentation for a previously undocumented option, while possibly
laudable, feels like taking a tangent to a tangent to me.  (And this
series is already long enough and is still hanging after two months,
so...)


Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default
  2020-02-09 15:59         ` Phillip Wood
@ 2020-02-13 18:52           ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-02-13 18:52 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder, Emily Shaffer

Hi Phillip,

On Sun, Feb 9, 2020 at 7:59 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Elijah
>
> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > Different rebase backends have different treatment for commits which
> > start empty (i.e. have no changes relative to their parent), and the
> > --keep-empty option was added at some point to allow adjusting behavior
> > for the interactive backend.
>
> --keep-empty was introduced by 90e1818f9a ("git-rebase: add keep_empty
> flag", 2012-04-20) and was respected for am based rebases as well
> (although the implementation was broken if there were any conflicts)

Interesting.  If you look closely at commit 90e1818f9a, however,
you'll note that they mentioned that format-patch and am were not
capable of handling the --keep-empty behavior, and so they instead
bypassed those commands and re-implemented a non-error-handling
version of the sequencer backend (simply cherry-picking all the
commits).  Further, that re-implementation ignored other options that
were passed and meant to be interpreted by format-patch or am due to
the fact that format-patch and am were bypassed.  Thus, by a strict
reading, it looks like --keep-empty cannot be considered to have ever
been respected by the am backend.

This was made more concrete by commit  002ee2fe6825 (builtin rebase:
support `keep-empty` option, 2018-09-04) when the --keep-empty flag
was explicitly coded to imply the sequencer backend, thus making it
clear that this option was incompatible with am-based rebases.

This was cemented further by commit 5dacd4abdd3c (git-rebase.txt:
document incompatible options, 2018-06-25), which made it clear to
users that the flags were incompatible.

So, yeah, my commit message isn't quite accurate, but it's not quite
right to claim that --keep-empty worked with the am backend either;
the reality is a bit messier to explain.

> > The handling of commits which start empty
> > is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
> > --allow-empty-message the default, 2018-06-27), which pointed out that
> > the behavior for various backends is often more happenstance than
> > design.  The specific change made in that commit is actually quite
> > relevant as well and much of the logic there directly applies here.
> >
> > It makes a lot of sense in 'git commit' to error out on the creation of
> > empty commits, unless an override flag is provided.  However, once
> > someone determines that there is a rare case that merits using the
> > manual override to create such a commit, it is somewhere between
> > annoying and harmful to have to take extra steps to keep such
> > intentional commits around.  Granted, empty commits are quite rare,
> > which is why handling of them doesn't get considered much and folks tend
> > to defer to existing (accidental) behavior and assume there was a reason
> > for it, leading them to just add flags (--keep-empty in this case) that
> > allow them to override the bad defaults.  Fix the interactive backend so
> > that --keep-empty is the default, much like we did with
> > --allow-empty-message.  The am backend should also be fixed to have
> > --keep-empty semantics for commits that start empty, but that is not
> > included in this patch other than a testcase documenting the failure.
>
> The detailed commit message is great, however I think it would be good
> to point out that this changes things so that --no-keep-empty is parsed
> but does nothing - it is no longer possible to get that behavior. I'm
> not  entirely sure about making --keep-empty hidden given that the am
> backend has a different behavior without it but as we're changing the
> default backend it probably does not matter and it looks like
> --keep-empty combined with an am specific option will still error out
> which is good.

As noted above, the --keep-empty option has always been orthogonal to
the am backend, even if commit messages early on might have implied
otherwise.  Since it was only relevant for sequencer backend(s) and
it's no longer relevant for the (now only) sequencer backend, I think
hiding the option is a good idea.

> Overall I like this change and it simplifies the implementation which is
> nice. Hopefully no one was relying on --no-keep-empty

:-)

> > Note that there was one test in t3421 which appears to have been written
> > expecting --keep-empty to not be the default as correct behavior.  This
> > test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
> > empty commits", 2013-06-06), which was part of a series focusing on
> > rebase topology and which had an interesting original cover letter at
> > https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
> > which noted
> >      Your input especially appreciated on whether you agree with the
> >      intent of the test cases.
> > and then went into a long example about how one of the many tests added
> > had several questions about whether it was correct.  As such, I believe
> > most the tests in that series were about testing rebase topology with as
> > many different flags as possible and were not trying to state in general
> > how those flags should behave otherwise.
> >
> > Signed-off-by: Elijah Newren <newren@gmail.com>
> > ---
> >   Documentation/git-rebase.txt      | 24 ++++++-----
> >   builtin/rebase.c                  | 28 ++++++++----
> >   rebase-interactive.c              |  7 +--
> >   rebase-interactive.h              |  2 +-
> >   sequencer.c                       | 20 +++------
> >   sequencer.h                       |  2 +-
> >   t/t3421-rebase-topology-linear.sh | 16 +++----
> >   t/t3424-rebase-empty.sh           | 72 +++++++++++++++++++++++++++++++
> >   t/t3427-rebase-subtree.sh         | 12 +++---
> >   9 files changed, 128 insertions(+), 55 deletions(-)
> >   create mode 100755 t/t3424-rebase-empty.sh
> >
> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> > index c83be7ffc2..1d19542d79 100644
> > --- a/Documentation/git-rebase.txt
> > +++ b/Documentation/git-rebase.txt
> > @@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.
> >       unchanged as a result.
> >
> >   --keep-empty::
> > -     Keep the commits that do not change anything from its
> > -     parents in the result.
> > +     No-op.  Rebasing commits that started empty (had no change
> > +     relative to their parent) used to fail and this option would
> > +     override that behavior, allowing commits with empty changes to
> > +     be rebased.  Now commits with no changes do not cause rebasing
> > +     to halt.
>
> Maybe mention that --no-keep-empty is no longer respected?

Possibly, but I'm somewhat of the mind that If this option wasn't
worth documenting before (as evidenced by the fact that it wasn't) and
wasn't all that interesting in the first place (the only thing it
could do was override a previous --keep-empty), why add the
documentation now --especially since it still only overrides a
previous --keep-empty and overriding a no-op just amounts to a
different-flavored no-op?

> > -See also INCOMPATIBLE OPTIONS below.
> > +See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
> >
> >   --allow-empty-message::
> >       No-op.  Rebasing commits with an empty message used to fail
> > @@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.
> >   Empty commits
> >   ~~~~~~~~~~~~~
> >
> > -The am backend drops any "empty" commits, regardless of whether the
> > -commit started empty (had no changes relative to its parent to
> > -start with) or ended empty (all changes were already applied
> > -upstream in other commits).
> > +The am backend unfortunately drops intentionally empty commits, i.e.
> > +commits that started empty, though these are rare in practice.  It
> > +also drops commits that become empty and has no option for controlling
> > +this behavior.
> >
> > -The interactive backend drops commits by default that
> > -started empty and halts if it hits a commit that ended up empty.
> > -The `--keep-empty` option exists for the interactive backend to allow
> > -it to keep commits that started empty.
> > +The interactive backend keeps intentionally empty commits.
> > +Unfortunately, it always halts whenever it runs across a commit that
> > +becomes empty, even when the rebase is not explicitly interactive.
> >
> >   Directory rename detection
> >   ~~~~~~~~~~~~~~~~~~~~~~~~~~
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index faa4e0d406..537b3241ce 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -77,7 +77,6 @@ struct rebase_options {
> >       const char *action;
> >       int signoff;
> >       int allow_rerere_autoupdate;
> > -     int keep_empty;
> >       int autosquash;
> >       char *gpg_sign_opt;
> >       int autostash;
> > @@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
> >
> >       git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
> >
> > -     flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
> >       flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
> >       flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
> >       flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
> > @@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,
> >       return ret;
> >   }
> >
> > +static int parse_opt_keep_empty(const struct option *opt, const char *arg,
> > +                             int unset)
> > +{
> > +     struct rebase_options *opts = opt->value;
> > +
> > +     BUG_ON_OPT_ARG(arg);
> > +
> > +     opts->type = REBASE_INTERACTIVE;
> > +     return 0;
> > +}
>
> This doesn't seem to handle --no-keep-empty (but looking through the
> patch that seems to be intentional)
>
> >   static const char * const builtin_rebase_interactive_usage[] = {
> >       N_("git rebase--interactive [<options>]"),
> >       NULL
> > @@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
> >       struct option options[] = {
> >               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
> >                          REBASE_FORCE),
> > -             OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
> > +             { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> > +                     N_("(DEPRECATED) keep empty commits"),
> > +                     PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> > +                     parse_opt_keep_empty },
> >               OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
> >                          N_("allow commits with empty messages"),
> >                          PARSE_OPT_HIDDEN),
> > @@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
> >               opts->allow_rerere_autoupdate ?
> >                       opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
> >                       "--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
> > -     add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
> >       add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
> >       add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
> >       add_var(&script_snippet, "cmd", opts->cmd);
> > @@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >                                "ignoring them"),
> >                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
> >               OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
> > -             OPT_BOOL('k', "keep-empty", &options.keep_empty,
> > -                      N_("preserve empty commits during rebase")),
> > +             { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> > +                     N_("(DEPRECATED) keep empty commits"),
> > +                     PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> > +                     parse_opt_keep_empty },
> >               OPT_BOOL(0, "autosquash", &options.autosquash,
> >                        N_("move commits that begin with "
> >                           "squash!/fixup! under -i")),
> > @@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >       if (!(options.flags & REBASE_NO_QUIET))
> >               argv_array_push(&options.git_am_opts, "-q");
> >
> > -     if (options.keep_empty)
> > -             imply_interactive(&options, "--keep-empty");
> > -
> >       if (gpg_sign) {
> >               free(options.gpg_sign_opt);
> >               options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
> > diff --git a/rebase-interactive.c b/rebase-interactive.c
> > index aa18ae82b7..7e7b4061bf 100644
> > --- a/rebase-interactive.c
> > +++ b/rebase-interactive.c
> > @@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
> >       return MISSING_COMMIT_CHECK_IGNORE;
> >   }
> >
> > -void append_todo_help(unsigned keep_empty, int command_count,
> > +void append_todo_help(int command_count,
> >                     const char *shortrevisions, const char *shortonto,
> >                     struct strbuf *buf)
> >   {
> > @@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,
> >                       "the rebase will be aborted.\n\n");
> >
> >       strbuf_add_commented_lines(buf, msg, strlen(msg));
> > -
> > -     if (!keep_empty) {
> > -             msg = _("Note that empty commits are commented out");
> > -             strbuf_add_commented_lines(buf, msg, strlen(msg));
> > -     }
> >   }
> >
> >   int edit_todo_list(struct repository *r, struct todo_list *todo_list,
> > diff --git a/rebase-interactive.h b/rebase-interactive.h
> > index 44dbb06311..05354ca341 100644
> > --- a/rebase-interactive.h
> > +++ b/rebase-interactive.h
> > @@ -5,7 +5,7 @@ struct strbuf;
> >   struct repository;
> >   struct todo_list;
> >
> > -void append_todo_help(unsigned keep_empty, int command_count,
> > +void append_todo_help(int command_count,
> >                     const char *shortrevisions, const char *shortonto,
> >                     struct strbuf *buf);
> >   int edit_todo_list(struct repository *r, struct todo_list *todo_list,
> > diff --git a/sequencer.c b/sequencer.c
> > index b9dbf1adb0..c21fc202b1 100644
> > --- a/sequencer.c
> > +++ b/sequencer.c
> > @@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,
> >                      struct replay_opts *opts,
> >                      struct commit *commit)
> >   {
> > -     int index_unchanged, empty_commit;
> > +     int index_unchanged, originally_empty;
> >
> >       /*
> >        * Three cases:
> > @@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,
> >       if (opts->keep_redundant_commits)
> >               return 1;
> >
> > -     empty_commit = is_original_commit_empty(commit);
> > -     if (empty_commit < 0)
> > -             return empty_commit;
> > -     if (!empty_commit)
> > +     originally_empty = is_original_commit_empty(commit);
> > +     if (originally_empty < 0)
> > +             return originally_empty;
> > +     if (!originally_empty)
> >               return 0;
> >       else
> >               return 1;
> > @@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
> >                                  struct rev_info *revs, struct strbuf *out,
> >                                  unsigned flags)
> >   {
> > -     int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
> >       int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
> >       int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
> >       struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
> > @@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
> >               if (!to_merge) {
> >                       /* non-merge commit: easy case */
> >                       strbuf_reset(&buf);
> > -                     if (!keep_empty && is_empty)
> > -                             strbuf_addf(&buf, "%c ", comment_line_char);
> >                       strbuf_addf(&buf, "%s %s %s", cmd_pick,
> >                                   oid_to_hex(&commit->object.oid),
> >                                   oneline.buf);
> > @@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
> >       struct pretty_print_context pp = {0};
> >       struct rev_info revs;
> >       struct commit *commit;
> > -     int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
> >       const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
> >       int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
> >
> > @@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
> >               return make_script_with_merges(&pp, &revs, out, flags);
> >
> >       while ((commit = get_revision(&revs))) {
> > -             int is_empty  = is_original_commit_empty(commit);
> > +             int is_empty = is_original_commit_empty(commit);
> >
> >               if (!is_empty && (commit->object.flags & PATCHSAME))
> >                       continue;
> > -             if (!keep_empty && is_empty)
> > -                     strbuf_addf(out, "%c ", comment_line_char);
> >               strbuf_addf(out, "%s %s ", insn,
> >                           oid_to_hex(&commit->object.oid));
> >               pretty_print_commit(&pp, commit, out);
> > @@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
> >
> >       todo_list_to_strbuf(r, todo_list, &buf, num, flags);
> >       if (flags & TODO_LIST_APPEND_TODO_HELP)
> > -             append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
> > +             append_todo_help(count_commands(todo_list),
> >                                shortrevisions, shortonto, &buf);
> >
> >       res = write_message(buf.buf, buf.len, file, 0);
> > diff --git a/sequencer.h b/sequencer.h
> > index 9f9ae291e3..c165e0ff25 100644
> > --- a/sequencer.h
> > +++ b/sequencer.h
> > @@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
> >   int sequencer_skip(struct repository *repo, struct replay_opts *opts);
> >   int sequencer_remove_state(struct replay_opts *opts);
> >
> > -#define TODO_LIST_KEEP_EMPTY (1U << 0)
> > +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
> >   #define TODO_LIST_SHORTEN_IDS (1U << 1)
> >   #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
> >   #define TODO_LIST_REBASE_MERGES (1U << 3)
> > diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
> > index 325072b0a3..57334dca7e 100755
> > --- a/t/t3421-rebase-topology-linear.sh
> > +++ b/t/t3421-rebase-topology-linear.sh
> > @@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '
> >   test_run_rebase () {
> >       result=$1
> >       shift
> > -     test_expect_$result "rebase $* drops empty commit" "
> > +     test_expect_$result "rebase $* keeps begin-empty commits" "
> >               reset_rebase &&
> > -             git rebase $* c l &&
> > -             test_cmp_rev c HEAD~2 &&
> > -             test_linear_range 'd l' c..
> > +             git rebase $* j l &&
> > +             test_cmp_rev c HEAD~4 &&
> > +             test_linear_range 'j d k l' c..
> >       "
> >   }
> > -test_run_rebase success ''
> > +test_run_rebase failure ''
> >   test_run_rebase success -m
> >   test_run_rebase success -i
> > -test_have_prereq !REBASE_P || test_run_rebase success -p
> > +test_have_prereq !REBASE_P || test_run_rebase failure -p
> >
> >   test_run_rebase () {
> >       result=$1
> > @@ -230,7 +230,7 @@ test_run_rebase () {
> >   test_run_rebase success ''
> >   test_run_rebase success -m
> >   test_run_rebase success -i
> > -test_have_prereq !REBASE_P || test_run_rebase failure -p
> > +test_have_prereq !REBASE_P || test_run_rebase success -p
> >
> >   test_run_rebase () {
> >       result=$1
> > @@ -245,7 +245,7 @@ test_run_rebase () {
> >   test_run_rebase success ''
> >   test_run_rebase success -m
> >   test_run_rebase success -i
> > -test_have_prereq !REBASE_P || test_run_rebase failure -p
> > +test_have_prereq !REBASE_P || test_run_rebase success -p
> >   test_run_rebase success --rebase-merges
> >
> >   #       m
> > diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
> > new file mode 100755
> > index 0000000000..22d97e143b
> > --- /dev/null
> > +++ b/t/t3424-rebase-empty.sh
> > @@ -0,0 +1,72 @@
> > +#!/bin/sh
> > +
> > +test_description='git rebase of commits that start or become empty'
> > +
> > +. ./test-lib.sh
> > +
> > +test_expect_success 'setup test repository' '
> > +     test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
> > +     test_write_lines A B C D E F G H I J >letters &&
> > +     git add numbers letters &&
> > +     git commit -m A &&
> > +
> > +     git branch upstream &&
> > +     git branch localmods &&
> > +
> > +     git checkout upstream &&
> > +     test_write_lines A B C D E >letters &&
> > +     git add letters &&
> > +     git commit -m B &&
> > +
> > +     test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
> > +     git add numbers &&
> > +     git commit -m C &&
> > +
> > +     git checkout localmods &&
> > +     test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
> > +     git add numbers &&
> > +     git commit -m C2 &&
> > +
> > +     git commit --allow-empty -m D &&
> > +
> > +     test_write_lines A B C D E >letters &&
> > +     git add letters &&
> > +     git commit -m "Five letters ought to be enough for anybody"
> > +'
> > +
> > +test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
> > +     test_when_finished "git rebase --abort" &&
> > +     git checkout -B testing localmods &&
> > +     # rebase (--am) should not drop commits that start empty
> > +     git rebase upstream &&
> > +
> > +     test_write_lines D C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +test_expect_failure 'rebase --merge with a variety of empty commits' '
> > +     test_when_finished "git rebase --abort" &&
> > +     git checkout -B testing localmods &&
> > +     # rebase --merge should not halt on the commit that becomes empty
> > +     git rebase --merge upstream &&
> > +
> > +     test_write_lines D C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>
> This doesn't do any harm but I'm not sure it's needed

Indeed, good catch.  I'll remove it.


Thanks for digging in and providing feedback!


Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
  2020-02-10 14:27         ` Phillip Wood
@ 2020-02-13 18:54           ` Elijah Newren
  2020-02-16 14:46             ` Phillip Wood
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-02-13 18:54 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder, Emily Shaffer

On Mon, Feb 10, 2020 at 6:27 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Elijah
>
> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > As established in the previous commit and commit b00bf1c9a8dd
> > (git-rebase: make --allow-empty-message the default, 2018-06-27), the
> > behavior for rebase with different backends in various edge or corner
> > cases is often more happenstance than design.  This commit addresses
> > another such corner case: commits which "become empty".
> >
> > A careful reader may note that there are two types of commits which would
> > become empty due to a rebase:
> >
> >    * [clean cherry-pick] Commits which are clean cherry-picks of upstream
> >      commits, as determined by `git log --cherry-mark ...`.  Re-applying
> >      these commits would result in an empty set of changes and a
> >      duplicative commit message; i.e. these are commits that have
> >      "already been applied" upstream.
> >
> >    * [become empty] Commits which are not empty to start, are not clean
> >      cherry-picks of upstream commits, but which still become empty after
> >      being rebased.  This happens e.g. when a commit has changes which
> >      are a strict subset of the changes in an upstream commit, or when
> >      the changes of a commit can be found spread across or among several
> >      upstream commits.
> >
> > Clearly, in both cases the changes in the commit in question are found
> > upstream already, but the commit message may not be in the latter case.
> >
> > When cherry-mark can determine a commit is already upstream, then
> > because of how cherry-mark works this means the upstream commit message
> > was about the *exact* same set of changes.  Thus, the commit messages
> > can be assumed to be fully interchangeable (and are in fact likely to be
> > completely identical).  As such, the clean cherry-pick case represents a
> > case when there is no information to be gained by keeping the extra
> > commit around.  All rebase types have always dropped these commits, and
> > no one to my knowledge has ever requested that we do otherwise.
> >
> > For many of the become empty cases (and likely even most), we will also
> > be able to drop the commit without loss of information -- but this isn't
> > quite always the case.  Since these commits represent cases that were
> > not clean cherry-picks, there is no upstream commit message explaining
> > the same set of changes.  Projects with good commit message hygiene will
> > likely have the explanation from our commit message contained within or
> > spread among the relevant upstream commits, but not all projects run
> > that way.  As such, the commit message of the commit being rebased may
> > have reasoning that suggests additional changes that should be made to
> > adapt to the new base, or it may have information that someone wants to
> > add as a note to another commit, or perhaps someone even wants to create
> > an empty commit with the commit message as-is.
> >
> > Junio commented on the "become-empty" types of commits as follows[1]:
> >
> >      WRT a change that ends up being empty (as opposed to a change that
> >      is empty from the beginning), I'd think that the current behaviour
> >      is desireable one.  "am" based rebase is solely to transplant an
> >      existing history and want to stop much less than "interactive" one
> >      whose purpose is to polish a series before making it publishable,
> >      and asking for confirmation ("this has become empty--do you want to
> >      drop it?") is more appropriate from the workflow point of view.
> >
> > [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
> >
> > I would simply add that his arguments for "am"-based rebases actually
> > apply to all non-explicitly-interactive rebases.  Also, since we are
> > stating that different cases should have different defaults, it may be
> > worth providing a flag to allow users to select which behavior they want
> > for these commits.
> >
> > Introduce a new command line flag for selecting the desired behavior:
> >      --empty={drop,keep,ask}
> > with the definitions:
> >      drop: drop commits which become empty
> >      keep: keep commits which become empty
> >      ask:  provide the user a chance to interact and pick what to do with
> >            commits which become empty on a case-by-case basis
> >
> > In line with Junio's suggestion, if the --empty flag is not specified,
> > pick defaults as follows:
> >      explicitly interactive: ask
> >      otherwise: drop
>
> Looking at the implementation there is a third option - if `--exec` is
> given without `-i` then the default is "keep". I'm not sure if having
> different defaults is convenient or confusing but don't feel that
> strongly about it.

Heh, in https://lore.kernel.org/git/404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com/
you argued that having different defaults was confusing and sounded
like you felt strongly about it.  Granted, there has been a lot of
simplification to the implementation (and description) since then but
I'm still inclined to go with the simpler and more easily explained
behavior for the defaults based on what you said there.

> I've got a few minor comments below (the mains ones
> are saying which commit has been dropped and testing the default
> behavior when --empty is not given) but basically I like the new patch.
> Thanks for working on this, the commit message does a good job of
> explaining the changes.

:-)

> > Signed-off-by: Elijah Newren <newren@gmail.com>
> > ---
> >   Documentation/git-rebase.txt | 27 ++++++++++++++++---
> >   builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++
> >   sequencer.c                  | 48 +++++++++++++++++++++++++--------
> >   sequencer.h                  |  1 +
> >   t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----
> >   t/t3427-rebase-subtree.sh    |  8 +++---
> >   6 files changed, 161 insertions(+), 25 deletions(-)
> >
> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
> > index 1d19542d79..551a91d764 100644
> > --- a/Documentation/git-rebase.txt
> > +++ b/Documentation/git-rebase.txt
> > @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.
> >       original branch. The index and working tree are also left
> >       unchanged as a result.
> >
> > +--empty={drop,keep,ask}::
> > +     How to handle commits that are not empty to start and are not
> > +     clean cherry-picks of any upstream commit, but which become
> > +     empty after rebasing (because they contain a subset of already
> > +     upstream changes).  With drop (the default), commits that
> > +     become empty are dropped.  With keep, such commits are kept.
> > +     With ask (implied by --interactive), the rebase will halt when
> > +     an empty commit is applied allowing you to choose whether to
> > +     drop it, edit files more, or just commit the empty changes.
>
> We should probably document the default for --exec without -i

I did, but I guess it wasn't clear enough.  Maybe I should add "Note
that other options like --exec will use the default of drop unless
-i/--interactive is specified."?

> > +Note that commits which start empty are kept, and commits which are
> > +clean cherry-picks (as determined by `git log --cherry-mark ...`) are
> > +always dropped.
> > ++
> > +See also INCOMPATIBLE OPTIONS below.
> > +
> >   --keep-empty::
> >       No-op.  Rebasing commits that started empty (had no change
> >       relative to their parent) used to fail and this option would
> > @@ -561,6 +577,7 @@ are incompatible with the following options:
> >    * --interactive
> >    * --exec
> >    * --keep-empty
> > + * --empty=
> >    * --edit-todo
> >    * --root when used in combination with --onto
> >
> > @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:
> >    * --preserve-merges and --interactive
> >    * --preserve-merges and --signoff
> >    * --preserve-merges and --rebase-merges
> > + * --preserve-merges and --empty=
> >    * --keep-base and --onto
> >    * --keep-base and --root
> >
> > @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It
> >   also drops commits that become empty and has no option for controlling
> >   this behavior.
> >
> > -The interactive backend keeps intentionally empty commits.
> > -Unfortunately, it always halts whenever it runs across a commit that
> > -becomes empty, even when the rebase is not explicitly interactive.
> > +The interactive backend keeps intentionally empty commits.  Similar to
> > +the am backend, by default the interactive backend drops commits that
> > +become empty unless -i/--interactive is specified (in which case it
> > +stops and asks the user what to do).  The interactive backend also has
> > +an --empty={drop,keep,ask} option for changing the behavior of
> > +handling commits that become empty.
> >
> >   Directory rename detection
> >   ~~~~~~~~~~~~~~~~~~~~~~~~~~
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index 537b3241ce..c299869e7b 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -50,8 +50,16 @@ enum rebase_type {
> >       REBASE_PRESERVE_MERGES
> >   };
> >
> > +enum empty_type {
> > +     EMPTY_UNSPECIFIED = -1,
> > +     EMPTY_DROP,
> > +     EMPTY_KEEP,
> > +     EMPTY_ASK
> > +};
> > +
> >   struct rebase_options {
> >       enum rebase_type type;
> > +     enum empty_type empty;
> >       const char *state_dir;
> >       struct commit *upstream;
> >       const char *upstream_name;
> > @@ -91,6 +99,7 @@ struct rebase_options {
> >
> >   #define REBASE_OPTIONS_INIT {                               \
> >               .type = REBASE_UNSPECIFIED,             \
> > +             .empty = EMPTY_UNSPECIFIED,             \
> >               .flags = REBASE_NO_QUIET,               \
> >               .git_am_opts = ARGV_ARRAY_INIT,         \
> >               .git_format_patch_opt = STRBUF_INIT     \
> > @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
> >               replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
> >       replay.allow_empty = 1;
> >       replay.allow_empty_message = opts->allow_empty_message;
> > +     replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
> > +     replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
> >       replay.verbose = opts->flags & REBASE_VERBOSE;
> >       replay.reschedule_failed_exec = opts->reschedule_failed_exec;
> >       replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> > @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
> >
> >       BUG_ON_OPT_ARG(arg);
> >
> > +     /*
> > +      * If we ever want to remap --keep-empty to --empty=keep, insert:
> > +      *      opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
> > +      */
> >       opts->type = REBASE_INTERACTIVE;
> >       return 0;
> >   }
> > @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
> >       return 0;
> >   }
> >
> > +static enum empty_type parse_empty_value(const char *value)
> > +{
> > +     if (!strcasecmp(value, "drop"))
> > +             return EMPTY_DROP;
> > +     else if (!strcasecmp(value, "keep"))
> > +             return EMPTY_KEEP;
> > +     else if (!strcasecmp(value, "ask"))
> > +             return EMPTY_ASK;
> > +
> > +     die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
> > +}
> > +
> > +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
> > +{
> > +     struct rebase_options *options = opt->value;
> > +     enum empty_type value = parse_empty_value(arg);
> > +
> > +     BUG_ON_OPT_NEG(unset);
> > +
> > +     options->empty = value;
> > +     return 0;
> > +}
> > +
> >   static void NORETURN error_on_missing_default_upstream(void)
> >   {
> >       struct branch *current_branch = branch_get(NULL);
> > @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >                                "ignoring them"),
> >                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
> >               OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
> > +             OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
> > +                            N_("how to handle empty commits"),
>
> Maybe we should say "how to handle commits that become empty" to
> distinguish them from commits that start empty which we always keep

Ooh, good catch; will fix.

> > +                            PARSE_OPT_NONEG, parse_opt_empty),
> >               { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
> >                       N_("(DEPRECATED) keep empty commits"),
> >                       PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
> > @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >       if (!(options.flags & REBASE_NO_QUIET))
> >               argv_array_push(&options.git_am_opts, "-q");
> >
> > +     if (options.empty != EMPTY_UNSPECIFIED)
> > +             imply_interactive(&options, "--empty");
> > +
> >       if (gpg_sign) {
> >               free(options.gpg_sign_opt);
> >               options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
> > @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >               break;
> >       }
> >
> > +     if (options.empty == EMPTY_UNSPECIFIED) {
> > +             if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
> > +                     options.empty = EMPTY_ASK;
> > +             else if (exec.nr > 0)
> > +                     options.empty = EMPTY_KEEP;
> > +             else
> > +                     options.empty = EMPTY_DROP;
> > +     }
> >       if (reschedule_failed_exec > 0 && !is_interactive(&options))
> >               die(_("--reschedule-failed-exec requires "
> >                     "--exec or --interactive"));
> > diff --git a/sequencer.c b/sequencer.c
> > index c21fc202b1..354d0b5a38 100644
> > --- a/sequencer.c
> > +++ b/sequencer.c
> > @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
> >   static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
> >   static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
> >   static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
> > +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
> > +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
> >
> >   static int git_sequencer_config(const char *k, const char *v, void *cb)
> >   {
> > @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)
> >   }
> >
> >   /*
> > - * Do we run "git commit" with "--allow-empty"?
> > + * Should empty commits be allowed?  Return status:
> > + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
> > + *     0: Halt on empty commit
> > + *     1: Allow empty commit
> > + *     2: Drop empty commit
> >    */
> >   static int allow_empty(struct repository *r,
> >                      struct replay_opts *opts,
> > @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,
> >       int index_unchanged, originally_empty;
> >
> >       /*
> > -      * Three cases:
> > +      * Four cases:
> >        *
> >        * (1) we do not allow empty at all and error out.
> >        *
> > -      * (2) we allow ones that were initially empty, but
> > -      * forbid the ones that become empty;
> > +      * (2) we allow ones that were initially empty, and
> > +      *     just drop the ones that become empty
> >        *
> > -      * (3) we allow both.
> > +      * (3) we allow ones that were initially empty, but
> > +      *     halt for the ones that become empty;
> > +      *
> > +      * (4) we allow both.
> >        */
> >       if (!opts->allow_empty)
> >               return 0; /* let "git commit" barf as necessary */
> > @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,
> >       originally_empty = is_original_commit_empty(commit);
> >       if (originally_empty < 0)
> >               return originally_empty;
> > -     if (!originally_empty)
> > -             return 0;
> > -     else
> > +     if (originally_empty)
> >               return 1;
> > +     else if (opts->drop_redundant_commits)
> > +             return 2;
> > +     else
> > +             return 0;
> >   }
> >
> >   static struct {
> > @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,
> >       char *author = NULL;
> >       struct commit_message msg = { NULL, NULL, NULL, NULL };
> >       struct strbuf msgbuf = STRBUF_INIT;
> > -     int res, unborn = 0, reword = 0, allow;
> > +     int res, unborn = 0, reword = 0, allow, drop_commit;
> >
> >       if (opts->no_commit) {
> >               /*
> > @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,
> >               goto leave;
> >       }
> >
> > +     drop_commit = 0;
> >       allow = allow_empty(r, opts, commit);
> >       if (allow < 0) {
> >               res = allow;
> >               goto leave;
> > -     } else if (allow)
> > +     } else if (allow == 1) {
> >               flags |= ALLOW_EMPTY;
> > -     if (!opts->no_commit) {
> > +     } else if (allow == 2) {
> > +             drop_commit = 1;
> > +             fprintf(stderr, _("No changes -- Patch already applied.\n"));
>
> nit pick - usually messages start with a lowercase letter. Would it be
> helpful to explicitly state which commit is being dropped as well as
> why? Something like
>    dropping <oid> <subject> - patch contents already upstream

I was actually just trying to mimic the am-backend here, and copied
its message verbatim for this case (see am_run() in builtin/am.c).
However, your version does seem more helpful and informative.  I'll
look into it implementing it here.

>
> > +     } // else allow == 0 and there's nothing special to do
>
> We don't use // for comments

Oops, sorry.  Will fix.

>
> > +     if (!opts->no_commit && !drop_commit) {
> >               if (author || command == TODO_REVERT || (flags & AMEND_MSG))
> >                       res = do_commit(r, msg_file, author, opts, flags);
> >               else
> > @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)
> >               if (file_exists(rebase_path_reschedule_failed_exec()))
> >                       opts->reschedule_failed_exec = 1;
> >
> > +             if (file_exists(rebase_path_drop_redundant_commits()))
> > +                     opts->drop_redundant_commits = 1;
> > +
> > +             if (file_exists(rebase_path_keep_redundant_commits()))
> > +                     opts->keep_redundant_commits = 1;
> > +
> >               read_strategy_opts(opts, &buf);
> >               strbuf_release(&buf);
> >
> > @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
> >               write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
> >       if (opts->signoff)
> >               write_file(rebase_path_signoff(), "--signoff\n");
> > +     if (opts->drop_redundant_commits)
> > +             write_file(rebase_path_drop_redundant_commits(), "%s", "");
> > +     if (opts->keep_redundant_commits)
> > +             write_file(rebase_path_keep_redundant_commits(), "%s", "");
> >       if (opts->reschedule_failed_exec)
> >               write_file(rebase_path_reschedule_failed_exec(), "%s", "");
> >
> > diff --git a/sequencer.h b/sequencer.h
> > index c165e0ff25..3b0ab9141f 100644
> > --- a/sequencer.h
> > +++ b/sequencer.h
> > @@ -39,6 +39,7 @@ struct replay_opts {
> >       int allow_rerere_auto;
> >       int allow_empty;
> >       int allow_empty_message;
> > +     int drop_redundant_commits;
> >       int keep_redundant_commits;
> >       int verbose;
> >       int quiet;
> > diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
> > index 22d97e143b..dcb4cb4751 100755
> > --- a/t/t3424-rebase-empty.sh
> > +++ b/t/t3424-rebase-empty.sh
> > @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '
> >       git commit -m "Five letters ought to be enough for anybody"
> >   '
> >
> > -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
> > +test_expect_failure 'rebase (am-backend)' '
> >       test_when_finished "git rebase --abort" &&
> >       git checkout -B testing localmods &&
> >       # rebase (--am) should not drop commits that start empty
> > @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
> >       test_cmp expect actual
> >   '
> >
> > -test_expect_failure 'rebase --merge with a variety of empty commits' '
> > -     test_when_finished "git rebase --abort" &&
> > +test_expect_success 'rebase --merge --empty=drop' '
> >       git checkout -B testing localmods &&
> > -     # rebase --merge should not halt on the commit that becomes empty
> > -     git rebase --merge upstream &&
> > +     git rebase --merge --empty=drop upstream &&
> > +
> > +     test_write_lines D C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +test_expect_success 'rebase --merge --empty=keep' '
> > +     git checkout -B testing localmods &&
> > +     git rebase --merge --empty=keep upstream &&
> > +
> > +     test_write_lines D C2 C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +test_expect_success 'rebase --merge --empty=ask' '
> > +     git checkout -B testing localmods &&
> > +     test_must_fail git rebase --merge --empty=ask upstream &&
> > +
> > +     git rebase --skip &&
> >
> >       test_write_lines D C B A >expect &&
> >       git log --format=%s >actual &&
> > @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '
> >
> >   GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
> >
> > -test_expect_success 'rebase --interactive with a variety of empty commits' '
> > +test_expect_success 'rebase --interactive --empty=drop' '
> > +     git checkout -B testing localmods &&
> > +     git rebase --interactive --empty=drop upstream &&
> > +
> > +     test_write_lines D C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +test_expect_success 'rebase --interactive --empty=keep' '
> > +     git checkout -B testing localmods &&
> > +     git rebase --interactive --empty=keep upstream &&
> > +
> > +     test_write_lines D C2 C B A >expect &&
> > +     git log --format=%s >actual &&
> > +     test_cmp expect actual
> > +'
> > +
> > +test_expect_success 'rebase --interactive --empty=ask' '
> >       git checkout -B testing localmods &&
> > -     test_must_fail git rebase --interactive upstream &&
> > +     test_must_fail git rebase --interactive --empty=ask upstream &&
> >
> >       git rebase --skip &&
>
> As the default if --empty is not given is supposed to vary depending on
> the other options given it would be good to test that I think

I'll add some tests.


Thanks for the thorough review!
Elijah

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v5 00/20] rebase: make the default backend configurable and change the default
  2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
                         ` (19 preceding siblings ...)
  2020-01-17 16:58       ` [PATCH v4 00/19] rebase: make the default backend configurable Phillip Wood
@ 2020-02-15 21:36       ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
                           ` (20 more replies)
  20 siblings, 21 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

This is v5 of en/rebase-backend based on v2.25.0. This series does a lot of
work around making the default rebase backend configurable, and switches the
default from the apply backend to the merge one.

Changes since v4:

 * Added a note about an unfortunately-timed Ctrl-C to the docs, as
   suggested by SZEDER.
 * Addressed feedback from Phillip
 * Added a new patch at the end renaming the two backends. PLEASE take a
   look at its commit message for rationale and let me know if you agree
   with it.

Note that in particular, the last patch at the end could have been partially
squashed into other patches, but if people objected to the patch in general
I wanted Junio to be able to take the rest of the series and just drop that
patch.

As with v4, there are some things mentioned earlier that are not addressed:

 * I did not remove the --am (now --apply) option as suggested by Phillip,
   since Junio and Phillip were still discussing whether it is
   wanted/needed. I kind of like having it, but have no strong opinion.
 * I noted the post-commit hook in the differences between backends. Emily
   is investigating what changes need to happen there, so I merely
   documented the existing differences.
 * I did not address the last two items Jonathan brought up as I couldn't
   find enough information to reproduce or understand the problems.

Elijah Newren (20):
  git-rebase.txt: update description of --allow-empty-message
  t3404: directly test the behavior of interest
  rebase (interactive-backend): make --keep-empty the default
  rebase (interactive-backend): fix handling of commits that become
    empty
  t3406: simplify an already simple test
  rebase, sequencer: remove the broken GIT_QUIET handling
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase: fix handling of restrict_revision
  t3432: make these tests work with either am or merge backends
  rebase: allow more types of rebases to fast-forward
  git-rebase.txt: add more details about behavioral differences of
    backends
  rebase: move incompatibility checks between backend options a bit
    earlier
  rebase: add an --am option
  git-prompt: change the prompt for interactive-based rebases
  rebase: drop '-i' from the reflog for interactive-based rebases
  rebase tests: mark tests specific to the am-backend with --am
  rebase tests: repeat some tests using the merge backend instead of am
  rebase: make the backend configurable via config setting
  rebase: change the default backend from "am" to "merge"
  rebase: rename the two primary rebase backends

 Documentation/config/rebase.txt        |   6 +
 Documentation/git-rebase.txt           | 170 +++++++++++++---
 builtin/rebase.c                       | 257 +++++++++++++++++--------
 contrib/completion/git-prompt.sh       |   6 +-
 rebase-interactive.c                   |   7 +-
 rebase-interactive.h                   |   2 +-
 sequencer.c                            |  82 +++++---
 sequencer.h                            |   3 +-
 t/t3400-rebase.sh                      |  40 +++-
 t/t3401-rebase-and-am-rename.sh        |   4 +-
 t/t3404-rebase-interactive.sh          |  19 +-
 t/t3406-rebase-message.sh              |  19 +-
 t/t3407-rebase-abort.sh                |   6 +-
 t/t3420-rebase-autostash.sh            |  22 +--
 t/t3421-rebase-topology-linear.sh      |  48 ++---
 t/t3424-rebase-empty.sh                | 126 ++++++++++++
 t/t3425-rebase-topology-merges.sh      |   8 +-
 t/t3427-rebase-subtree.sh              |  12 +-
 t/t3432-rebase-fast-forward.sh         |  54 +++---
 t/t5407-post-rewrite-hook.sh           |  16 +-
 t/t5520-pull.sh                        |  27 ++-
 t/t6047-diff3-conflict-markers.sh      |  13 +-
 t/t7512-status-help.sh                 |  12 +-
 t/t9106-git-svn-commit-diff-clobber.sh |   3 +-
 t/t9903-bash-prompt.sh                 |   8 +-
 25 files changed, 693 insertions(+), 277 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh


base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5
Pull-Request: https://github.com/git/git/pull/679

Range-diff vs v4:

  1:  3ea48d53940 =  1:  3ea48d53940 git-rebase.txt: update description of --allow-empty-message
  2:  10fdd162a05 =  2:  10fdd162a05 t3404: directly test the behavior of interest
  3:  179f82ab83e !  3:  74cab9a32b3 rebase (interactive-backend): make --keep-empty the default
     @@ -4,13 +4,13 @@
      
          Different rebase backends have different treatment for commits which
          start empty (i.e. have no changes relative to their parent), and the
     -    --keep-empty option was added at some point to allow adjusting behavior
     -    for the interactive backend.  The handling of commits which start empty
     -    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
     -    --allow-empty-message the default, 2018-06-27), which pointed out that
     -    the behavior for various backends is often more happenstance than
     -    design.  The specific change made in that commit is actually quite
     -    relevant as well and much of the logic there directly applies here.
     +    --keep-empty option was added at some point to allow adjusting behavior.
     +    The handling of commits which start empty is actually quite similar to
     +    commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
     +    2018-06-27), which pointed out that the behavior for various backends is
     +    often more happenstance than design.  The specific change made in that
     +    commit is actually quite relevant as well and much of the logic there
     +    directly applies here.
      
          It makes a lot of sense in 'git commit' to error out on the creation of
          empty commits, unless an override flag is provided.  However, once
     @@ -405,8 +405,6 @@
      +	test_cmp expect actual
      +'
      +
     -+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
     -+
      +test_expect_success 'rebase --interactive with a variety of empty commits' '
      +	git checkout -B testing localmods &&
      +	test_must_fail git rebase --interactive upstream &&
  4:  c9542a2abe0 !  4:  54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty
     @@ -98,6 +98,8 @@
      +	With ask (implied by --interactive), the rebase will halt when
      +	an empty commit is applied allowing you to choose whether to
      +	drop it, edit files more, or just commit the empty changes.
     ++	Other options, like --exec, will use the default of drop unless
     ++	-i/--interactive is explicitly specified.
      ++
      +Note that commits which start empty are kept, and commits which are
      +clean cherry-picks (as determined by `git log --cherry-mark ...`) are
     @@ -224,7 +226,7 @@
       			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
       		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
      +		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
     -+			       N_("how to handle empty commits"),
     ++			       N_("how to handle commits that become empty"),
      +			       PARSE_OPT_NONEG, parse_opt_empty),
       		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
       			N_("(DEPRECATED) keep empty commits"),
     @@ -342,8 +344,10 @@
      -	if (!opts->no_commit) {
      +	} else if (allow == 2) {
      +		drop_commit = 1;
     -+		fprintf(stderr, _("No changes -- Patch already applied.\n"));
     -+	} // else allow == 0 and there's nothing special to do
     ++		fprintf(stderr,
     ++			_("dropping %s %s -- patch contents already upstream\n"),
     ++			oid_to_hex(&commit->object.oid), msg.subject);
     ++	} /* else allow == 0 and there's nothing special to do */
      +	if (!opts->no_commit && !drop_commit) {
       		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
       			res = do_commit(r, msg_file, author, opts, flags);
     @@ -404,9 +408,7 @@
      -test_expect_failure 'rebase --merge with a variety of empty commits' '
      -	test_when_finished "git rebase --abort" &&
      +test_expect_success 'rebase --merge --empty=drop' '
     - 	git checkout -B testing localmods &&
     --	# rebase --merge should not halt on the commit that becomes empty
     --	git rebase --merge upstream &&
     ++	git checkout -B testing localmods &&
      +	git rebase --merge --empty=drop upstream &&
      +
      +	test_write_lines D C B A >expect &&
     @@ -414,6 +416,17 @@
      +	test_cmp expect actual
      +'
      +
     ++test_expect_success 'rebase --merge uses default of --empty=drop' '
     + 	git checkout -B testing localmods &&
     +-	# rebase --merge should not halt on the commit that becomes empty
     + 	git rebase --merge upstream &&
     + 
     + 	test_write_lines D C B A >expect &&
     +@@
     + 	test_cmp expect actual
     + '
     + 
     +-test_expect_success 'rebase --interactive with a variety of empty commits' '
      +test_expect_success 'rebase --merge --empty=keep' '
      +	git checkout -B testing localmods &&
      +	git rebase --merge --empty=keep upstream &&
     @@ -428,14 +441,12 @@
      +	test_must_fail git rebase --merge --empty=ask upstream &&
      +
      +	git rebase --skip &&
     - 
     - 	test_write_lines D C B A >expect &&
     - 	git log --format=%s >actual &&
     -@@
     - 
     - GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
     - 
     --test_expect_success 'rebase --interactive with a variety of empty commits' '
     ++
     ++	test_write_lines D C B A >expect &&
     ++	git log --format=%s >actual &&
     ++	test_cmp expect actual
     ++'
     ++
      +test_expect_success 'rebase --interactive --empty=drop' '
      +	git checkout -B testing localmods &&
      +	git rebase --interactive --empty=drop upstream &&
     @@ -455,11 +466,19 @@
      +'
      +
      +test_expect_success 'rebase --interactive --empty=ask' '
     - 	git checkout -B testing localmods &&
     --	test_must_fail git rebase --interactive upstream &&
     ++	git checkout -B testing localmods &&
      +	test_must_fail git rebase --interactive --empty=ask upstream &&
     - 
     - 	git rebase --skip &&
     ++
     ++	git rebase --skip &&
     ++
     ++	test_write_lines D C B A >expect &&
     ++	git log --format=%s >actual &&
     ++	test_cmp expect actual
     ++'
     ++
     ++test_expect_success 'rebase --interactive uses default of --empty=ask' '
     + 	git checkout -B testing localmods &&
     + 	test_must_fail git rebase --interactive upstream &&
       
      
       diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
  5:  9f66229d5cc =  5:  f5170e71510 t3406: simplify an already simple test
  6:  8d731fa39c3 =  6:  2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling
  7:  b6b6597eef8 =  7:  3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer
  8:  0acefa988b1 =  8:  4e6f5ce8c46 rebase: fix handling of restrict_revision
  9:  8c5b5b51330 =  9:  bcd04ec4ded t3432: make these tests work with either am or merge backends
 10:  b8c087d6fb1 = 10:  45eb87b36b0 rebase: allow more types of rebases to fast-forward
 11:  b50a1741e0b = 11:  11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends
 12:  58e6e4ffb3f = 12:  0c921919dce rebase: move incompatibility checks between backend options a bit earlier
 13:  5478c730aca = 13:  de1b0aaca88 rebase: add an --am option
 14:  db5e29bd818 = 14:  e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases
 15:  413e190ac9a ! 15:  f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases
     @@ -54,17 +54,6 @@
       		 */
       		return error(_("%s: Unable to write new index file"),
       			_(action_name(opts)));
     -@@
     - 	int next = todo_list->current, offset, fd;
     - 
     - 	/*
     --	 * rebase -i writes "git-rebase-todo" without the currently executing
     --	 * command, appending it to "done" instead.
     -+	 * interactive backend writes "git-rebase-todo" without the currently
     -+	 * executing command, appending it to "done" instead.
     - 	 */
     - 	if (is_rebase_i(opts))
     - 		next++;
      @@
       		return error(_("illegal label name: '%.*s'"), len, name);
       
 16:  170be283a85 ! 16:  56486d4d694 rebase tests: mark tests specific to the am-backend with --am
     @@ -173,6 +173,163 @@
       testrebase " --interactive" .git/rebase-merge
       
      
     + diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
     + --- a/t/t3421-rebase-topology-linear.sh
     + +++ b/t/t3421-rebase-topology-linear.sh
     +@@
     + 		test_linear_range 'd e' c..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_cmp_rev e HEAD
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'd e' b..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success --fork-point
     + test_run_rebase success -m
     + test_run_rebase success -i
     +@@
     + 		test_linear_range 'd e' branch-b..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success --fork-point
     + test_run_rebase success -m
     + test_run_rebase success -i
     +@@
     + 		test_cmp_rev e HEAD
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success --fork-point
     + test_run_rebase success -m
     + test_run_rebase success -i
     +@@
     + 		test_linear_range 'd i' h..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'd' h..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'd i' f..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'd gp i' h..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'j d k l' c..
     + 	"
     + }
     +-test_run_rebase failure ''
     ++test_run_rebase failure --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase failure -p
     +@@
     + 		test_linear_range 'd k l' c..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'd k l' j..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'x y' c..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 		test_linear_range 'x y' c..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase failure -p
     +@@
     + 		test_linear_range 'x y' m..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase success -p
     +@@
     + 	"
     + }
     + 
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase failure -p
     +@@
     + 		test_linear_range 'x y' m..
     + 	"
     + }
     +-test_run_rebase success ''
     ++test_run_rebase success --am
     + test_run_rebase success -m
     + test_run_rebase success -i
     + test_have_prereq !REBASE_P || test_run_rebase failure -p
     +
       diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
       --- a/t/t3425-rebase-topology-merges.sh
       +++ b/t/t3425-rebase-topology-merges.sh
 17:  1e3d4066c40 = 17:  dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am
 18:  9b4ac83d2d0 = 18:  ba1f2699b39 rebase: make the backend configurable via config setting
 19:  859a4a94d7f ! 19:  59faaa2f920 rebase: change the default backend from "am" to "merge"
     @@ -13,6 +13,7 @@
              patches incorrectly.[2]
            * lack of access to original commits means that conflict marker
              annotation has less information available.
     +      * the am backend has safety problems with an ill-timed interrupt.
      
          Also, the merge/interactive backend have far more abilities, appear to
          currently have a slight performance advantage[3] and have room for more
     @@ -38,6 +39,24 @@
       +
       Note that a rebase merge works by replaying each commit from the working
       branch on top of the <upstream> branch.  Because of this, when a merge
     +@@
     + should have the same behavior, though it is not clear which one is
     + correct.
     + 
     ++Interruptability
     ++~~~~~~~~~~~~~~~~
     ++
     ++The am backend has safety problems with an ill-timed interrupt; if the
     ++user presses Ctrl-C at the wrong time to try to abort the rebase, the
     ++rebase can enter a state where it cannot be aborted with a subsequent
     ++`git rebase --abort`.  The interactive backend does not appear to
     ++suffer from the same shortcoming.  (See
     ++https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for
     ++details.)
     ++
     + Miscellaneous differences
     + ~~~~~~~~~~~~~~~~~~~~~~~~~
     + 
      
       diff --git a/builtin/rebase.c b/builtin/rebase.c
       --- a/builtin/rebase.c
  -:  ----------- > 20:  ad8339aebf2 rebase: rename the two primary rebase backends

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 161+ messages in thread

* [PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 02/20] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
                           ` (19 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the
default", 2018-06-27) made --allow-empty-message the default and thus
turned --allow-empty-message into a no-op but did not update the
documentation to reflect this.  Update the documentation now, and hide
the option from the normal -h output since it is not useful.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt |  7 ++++---
 builtin/rebase.c             | 12 +++++++-----
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 0c4f038dd60..c83be7ffc20 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below.
 See also INCOMPATIBLE OPTIONS below.
 
 --allow-empty-message::
-	By default, rebasing commits with an empty message will fail.
-	This option overrides that behavior, allowing commits with empty
-	messages to be rebased.
+	No-op.  Rebasing commits with an empty message used to fail
+	and this option would override that behavior, allowing commits
+	with empty messages to be rebased.  Now commits with an empty
+	message do not cause rebasing to halt.
 +
 See also INCOMPATIBLE OPTIONS below.
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 8081741f8aa..faa4e0d4065 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
 		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
-		OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
-			 N_("allow commits with empty messages")),
+		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
+			   N_("allow commits with empty messages"),
+			   PARSE_OPT_HIDDEN),
 		OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
 		OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
 			 N_("keep original branch points of cousins")),
@@ -1495,9 +1496,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
 				N_("add exec lines after each commit of the "
 				   "editable list")),
-		OPT_BOOL(0, "allow-empty-message",
-			 &options.allow_empty_message,
-			 N_("allow rebasing commits with empty messages")),
+		OPT_BOOL_F(0, "allow-empty-message",
+			   &options.allow_empty_message,
+			   N_("allow rebasing commits with empty messages"),
+			   PARSE_OPT_HIDDEN),
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 02/20] t3404: directly test the behavior of interest
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 03/20] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
                           ` (18 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3404.3 is a simple test added by commit d078c3910689 ("t3404: todo list
with commented-out commands only aborts", 2018-08-10) which was designed
to test a todo list that only contained commented-out commands.  There
were two problems with this test: (1) its title did not reflect the
purpose of the test, and (2) it tested the desired behavior through a
side-effect of other functionality instead of directly testing the
desired behavior discussed in the commit message.

Modify the test to directly test the desired behavior and update the
test title.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3404-rebase-interactive.sh | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ae6e55ce79a..c41531f3490 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -72,15 +72,16 @@ test_expect_success 'rebase --keep-empty' '
 	test_line_count = 6 actual
 '
 
-test_expect_success 'rebase -i with empty HEAD' '
+test_expect_success 'rebase -i with empty todo list' '
 	cat >expect <<-\EOF &&
 	error: nothing to do
 	EOF
 	(
 		set_fake_editor &&
-		test_must_fail env FAKE_LINES="1 exec_true" \
-			git rebase -i HEAD^ >actual 2>&1
+		test_must_fail env FAKE_LINES="#" \
+			git rebase -i HEAD^ >output 2>&1
 	) &&
+	tail -n 1 output >actual &&  # Ignore output about changing todo list
 	test_i18ncmp expect actual
 '
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 03/20] rebase (interactive-backend): make --keep-empty the default
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 02/20] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 04/20] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
                           ` (17 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior.
The handling of commits which start empty is actually quite similar to
commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
2018-06-27), which pointed out that the behavior for various backends is
often more happenstance than design.  The specific change made in that
commit is actually quite relevant as well and much of the logic there
directly applies here.

It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided.  However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around.  Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults.  Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message.  The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.

Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior.  This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
    Your input especially appreciated on whether you agree with the
    intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct.  As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt      | 24 ++++++-----
 builtin/rebase.c                  | 28 +++++++++----
 rebase-interactive.c              |  7 +---
 rebase-interactive.h              |  2 +-
 sequencer.c                       | 20 ++++-----
 sequencer.h                       |  2 +-
 t/t3421-rebase-topology-linear.sh | 16 +++----
 t/t3424-rebase-empty.sh           | 70 +++++++++++++++++++++++++++++++
 t/t3427-rebase-subtree.sh         | 12 +++---
 9 files changed, 126 insertions(+), 55 deletions(-)
 create mode 100755 t/t3424-rebase-empty.sh

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index c83be7ffc20..1d19542d79f 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.
 	unchanged as a result.
 
 --keep-empty::
-	Keep the commits that do not change anything from its
-	parents in the result.
+	No-op.  Rebasing commits that started empty (had no change
+	relative to their parent) used to fail and this option would
+	override that behavior, allowing commits with empty changes to
+	be rebased.  Now commits with no changes do not cause rebasing
+	to halt.
 +
-See also INCOMPATIBLE OPTIONS below.
+See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
 
 --allow-empty-message::
 	No-op.  Rebasing commits with an empty message used to fail
@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.
 Empty commits
 ~~~~~~~~~~~~~
 
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
+The am backend unfortunately drops intentionally empty commits, i.e.
+commits that started empty, though these are rare in practice.  It
+also drops commits that become empty and has no option for controlling
+this behavior.
 
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
+The interactive backend keeps intentionally empty commits.
+Unfortunately, it always halts whenever it runs across a commit that
+becomes empty, even when the rebase is not explicitly interactive.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/rebase.c b/builtin/rebase.c
index faa4e0d4065..537b3241ce3 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -77,7 +77,6 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
-	int keep_empty;
 	int autosquash;
 	char *gpg_sign_opt;
 	int autostash;
@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
 
 	git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
-	flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,
 	return ret;
 }
 
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+				int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_INTERACTIVE;
+	return 0;
+}
+
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
 			   REBASE_FORCE),
-		OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
 			   N_("allow commits with empty messages"),
 			   PARSE_OPT_HIDDEN),
@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		opts->allow_rerere_autoupdate ?
 			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
 			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
 	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_BOOL('k', "keep-empty", &options.keep_empty,
-			 N_("preserve empty commits during rebase")),
+		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+			N_("(DEPRECATED) keep empty commits"),
+			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+			parse_opt_keep_empty },
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
-	if (options.keep_empty)
-		imply_interactive(&options, "--keep-empty");
-
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
diff --git a/rebase-interactive.c b/rebase-interactive.c
index aa18ae82b72..7e7b4061bfb 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
 	return MISSING_COMMIT_CHECK_IGNORE;
 }
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf)
 {
@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,
 			"the rebase will be aborted.\n\n");
 
 	strbuf_add_commented_lines(buf, msg, strlen(msg));
-
-	if (!keep_empty) {
-		msg = _("Note that empty commits are commented out");
-		strbuf_add_commented_lines(buf, msg, strlen(msg));
-	}
 }
 
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/rebase-interactive.h b/rebase-interactive.h
index 44dbb06311a..05354ca3417 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -5,7 +5,7 @@ struct strbuf;
 struct repository;
 struct todo_list;
 
-void append_todo_help(unsigned keep_empty, int command_count,
+void append_todo_help(int command_count,
 		      const char *shortrevisions, const char *shortonto,
 		      struct strbuf *buf);
 int edit_todo_list(struct repository *r, struct todo_list *todo_list,
diff --git a/sequencer.c b/sequencer.c
index b9dbf1adb07..c21fc202b1c 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,
 		       struct replay_opts *opts,
 		       struct commit *commit)
 {
-	int index_unchanged, empty_commit;
+	int index_unchanged, originally_empty;
 
 	/*
 	 * Three cases:
@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,
 	if (opts->keep_redundant_commits)
 		return 1;
 
-	empty_commit = is_original_commit_empty(commit);
-	if (empty_commit < 0)
-		return empty_commit;
-	if (!empty_commit)
+	originally_empty = is_original_commit_empty(commit);
+	if (originally_empty < 0)
+		return originally_empty;
+	if (!originally_empty)
 		return 0;
 	else
 		return 1;
@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 				   struct rev_info *revs, struct strbuf *out,
 				   unsigned flags)
 {
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 	int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
 	int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
 	struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 		if (!to_merge) {
 			/* non-merge commit: easy case */
 			strbuf_reset(&buf);
-			if (!keep_empty && is_empty)
-				strbuf_addf(&buf, "%c ", comment_line_char);
 			strbuf_addf(&buf, "%s %s %s", cmd_pick,
 				    oid_to_hex(&commit->object.oid),
 				    oneline.buf);
@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 	struct pretty_print_context pp = {0};
 	struct rev_info revs;
 	struct commit *commit;
-	int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
 	const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 	int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
 
@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 		return make_script_with_merges(&pp, &revs, out, flags);
 
 	while ((commit = get_revision(&revs))) {
-		int is_empty  = is_original_commit_empty(commit);
+		int is_empty = is_original_commit_empty(commit);
 
 		if (!is_empty && (commit->object.flags & PATCHSAME))
 			continue;
-		if (!keep_empty && is_empty)
-			strbuf_addf(out, "%c ", comment_line_char);
 		strbuf_addf(out, "%s %s ", insn,
 			    oid_to_hex(&commit->object.oid));
 		pretty_print_commit(&pp, commit, out);
@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
 
 	todo_list_to_strbuf(r, todo_list, &buf, num, flags);
 	if (flags & TODO_LIST_APPEND_TODO_HELP)
-		append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+		append_todo_help(count_commands(todo_list),
 				 shortrevisions, shortonto, &buf);
 
 	res = write_message(buf.buf, buf.len, file, 0);
diff --git a/sequencer.h b/sequencer.h
index 9f9ae291e3c..c165e0ff254 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
 int sequencer_skip(struct repository *repo, struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-#define TODO_LIST_KEEP_EMPTY (1U << 0)
+/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
 #define TODO_LIST_SHORTEN_IDS (1U << 1)
 #define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
 #define TODO_LIST_REBASE_MERGES (1U << 3)
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 325072b0a33..57334dca7e4 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '
 test_run_rebase () {
 	result=$1
 	shift
-	test_expect_$result "rebase $* drops empty commit" "
+	test_expect_$result "rebase $* keeps begin-empty commits" "
 		reset_rebase &&
-		git rebase $* c l &&
-		test_cmp_rev c HEAD~2 &&
-		test_linear_range 'd l' c..
+		git rebase $* j l &&
+		test_cmp_rev c HEAD~4 &&
+		test_linear_range 'j d k l' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase failure ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
+test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
 	result=$1
@@ -230,7 +230,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
 	result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
+test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
new file mode 100755
index 00000000000..3b716e980e7
--- /dev/null
+++ b/t/t3424-rebase-empty.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+test_description='git rebase of commits that start or become empty'
+
+. ./test-lib.sh
+
+test_expect_success 'setup test repository' '
+	test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+	test_write_lines A B C D E F G H I J >letters &&
+	git add numbers letters &&
+	git commit -m A &&
+
+	git branch upstream &&
+	git branch localmods &&
+
+	git checkout upstream &&
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m B &&
+
+	test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
+	git add numbers &&
+	git commit -m C &&
+
+	git checkout localmods &&
+	test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
+	git add numbers &&
+	git commit -m C2 &&
+
+	git commit --allow-empty -m D &&
+
+	test_write_lines A B C D E >letters &&
+	git add letters &&
+	git commit -m "Five letters ought to be enough for anybody"
+'
+
+test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
+	test_when_finished "git rebase --abort" &&
+	git checkout -B testing localmods &&
+	# rebase (--am) should not drop commits that start empty
+	git rebase upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_failure 'rebase --merge with a variety of empty commits' '
+	test_when_finished "git rebase --abort" &&
+	git checkout -B testing localmods &&
+	# rebase --merge should not halt on the commit that becomes empty
+	git rebase --merge upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive with a variety of empty commits' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --interactive upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index bec48e6a1f9..8dceef61cff 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
+test_expect_success 'Rebase -Xsubtree --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
-	git rebase --continue &&
+	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
 	verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
 	verbose test "$(commit_message HEAD)" = "Empty commit"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 04/20] rebase (interactive-backend): fix handling of commits that become empty
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (2 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 03/20] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 05/20] t3406: simplify an already simple test Elijah Newren via GitGitGadget
                           ` (16 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design.  This commit addresses
another such corner case: commits which "become empty".

A careful reader may note that there are two types of commits which would
become empty due to a rebase:

  * [clean cherry-pick] Commits which are clean cherry-picks of upstream
    commits, as determined by `git log --cherry-mark ...`.  Re-applying
    these commits would result in an empty set of changes and a
    duplicative commit message; i.e. these are commits that have
    "already been applied" upstream.

  * [become empty] Commits which are not empty to start, are not clean
    cherry-picks of upstream commits, but which still become empty after
    being rebased.  This happens e.g. when a commit has changes which
    are a strict subset of the changes in an upstream commit, or when
    the changes of a commit can be found spread across or among several
    upstream commits.

Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.

When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes.  Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical).  As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around.  All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.

For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case.  Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes.  Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way.  As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.

Junio commented on the "become-empty" types of commits as follows[1]:

    WRT a change that ends up being empty (as opposed to a change that
    is empty from the beginning), I'd think that the current behaviour
    is desireable one.  "am" based rebase is solely to transplant an
    existing history and want to stop much less than "interactive" one
    whose purpose is to polish a series before making it publishable,
    and asking for confirmation ("this has become empty--do you want to
    drop it?") is more appropriate from the workflow point of view.

[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/

I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases.  Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.

Introduce a new command line flag for selecting the desired behavior:
    --empty={drop,keep,ask}
with the definitions:
    drop: drop commits which become empty
    keep: keep commits which become empty
    ask:  provide the user a chance to interact and pick what to do with
          commits which become empty on a case-by-case basis

In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
    explicitly interactive: ask
    otherwise: drop

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 29 ++++++++++++++--
 builtin/rebase.c             | 52 ++++++++++++++++++++++++++++
 sequencer.c                  | 50 +++++++++++++++++++++------
 sequencer.h                  |  1 +
 t/t3424-rebase-empty.sh      | 66 +++++++++++++++++++++++++++++++++---
 t/t3427-rebase-subtree.sh    |  8 ++---
 6 files changed, 183 insertions(+), 23 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1d19542d79f..e1c6f918013 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,24 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--empty={drop,keep,ask}::
+	How to handle commits that are not empty to start and are not
+	clean cherry-picks of any upstream commit, but which become
+	empty after rebasing (because they contain a subset of already
+	upstream changes).  With drop (the default), commits that
+	become empty are dropped.  With keep, such commits are kept.
+	With ask (implied by --interactive), the rebase will halt when
+	an empty commit is applied allowing you to choose whether to
+	drop it, edit files more, or just commit the empty changes.
+	Other options, like --exec, will use the default of drop unless
+	-i/--interactive is explicitly specified.
++
+Note that commits which start empty are kept, and commits which are
+clean cherry-picks (as determined by `git log --cherry-mark ...`) are
+always dropped.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --keep-empty::
 	No-op.  Rebasing commits that started empty (had no change
 	relative to their parent) used to fail and this option would
@@ -561,6 +579,7 @@ are incompatible with the following options:
  * --interactive
  * --exec
  * --keep-empty
+ * --empty=
  * --edit-todo
  * --root when used in combination with --onto
 
@@ -569,6 +588,7 @@ In addition, the following pairs of options are incompatible:
  * --preserve-merges and --interactive
  * --preserve-merges and --signoff
  * --preserve-merges and --rebase-merges
+ * --preserve-merges and --empty=
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -585,9 +605,12 @@ commits that started empty, though these are rare in practice.  It
 also drops commits that become empty and has no option for controlling
 this behavior.
 
-The interactive backend keeps intentionally empty commits.
-Unfortunately, it always halts whenever it runs across a commit that
-becomes empty, even when the rebase is not explicitly interactive.
+The interactive backend keeps intentionally empty commits.  Similar to
+the am backend, by default the interactive backend drops commits that
+become empty unless -i/--interactive is specified (in which case it
+stops and asks the user what to do).  The interactive backend also has
+an --empty={drop,keep,ask} option for changing the behavior of
+handling commits that become empty.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 537b3241ce3..669690f9664 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -50,8 +50,16 @@ enum rebase_type {
 	REBASE_PRESERVE_MERGES
 };
 
+enum empty_type {
+	EMPTY_UNSPECIFIED = -1,
+	EMPTY_DROP,
+	EMPTY_KEEP,
+	EMPTY_ASK
+};
+
 struct rebase_options {
 	enum rebase_type type;
+	enum empty_type empty;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -91,6 +99,7 @@ struct rebase_options {
 
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
+		.empty = EMPTY_UNSPECIFIED,	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 		replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
 	replay.allow_empty = 1;
 	replay.allow_empty_message = opts->allow_empty_message;
+	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
 
 	BUG_ON_OPT_ARG(arg);
 
+	/*
+	 * If we ever want to remap --keep-empty to --empty=keep, insert:
+	 * 	opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
+	 */
 	opts->type = REBASE_INTERACTIVE;
 	return 0;
 }
@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static enum empty_type parse_empty_value(const char *value)
+{
+	if (!strcasecmp(value, "drop"))
+		return EMPTY_DROP;
+	else if (!strcasecmp(value, "keep"))
+		return EMPTY_KEEP;
+	else if (!strcasecmp(value, "ask"))
+		return EMPTY_ASK;
+
+	die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *options = opt->value;
+	enum empty_type value = parse_empty_value(arg);
+
+	BUG_ON_OPT_NEG(unset);
+
+	options->empty = value;
+	return 0;
+}
+
 static void NORETURN error_on_missing_default_upstream(void)
 {
 	struct branch *current_branch = branch_get(NULL);
@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				 "ignoring them"),
 			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
+		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
+			       N_("how to handle commits that become empty"),
+			       PARSE_OPT_NONEG, parse_opt_empty),
 		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
 			N_("(DEPRECATED) keep empty commits"),
 			PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (!(options.flags & REBASE_NO_QUIET))
 		argv_array_push(&options.git_am_opts, "-q");
 
+	if (options.empty != EMPTY_UNSPECIFIED)
+		imply_interactive(&options, "--empty");
+
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
 		options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
+	if (options.empty == EMPTY_UNSPECIFIED) {
+		if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+			options.empty = EMPTY_ASK;
+		else if (exec.nr > 0)
+			options.empty = EMPTY_KEEP;
+		else
+			options.empty = EMPTY_DROP;
+	}
 	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
diff --git a/sequencer.c b/sequencer.c
index c21fc202b1c..fdb8f91fbce 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
 static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
 static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
 static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
 
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)
 }
 
 /*
- * Do we run "git commit" with "--allow-empty"?
+ * Should empty commits be allowed?  Return status:
+ *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
+ *     0: Halt on empty commit
+ *     1: Allow empty commit
+ *     2: Drop empty commit
  */
 static int allow_empty(struct repository *r,
 		       struct replay_opts *opts,
@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,
 	int index_unchanged, originally_empty;
 
 	/*
-	 * Three cases:
+	 * Four cases:
 	 *
 	 * (1) we do not allow empty at all and error out.
 	 *
-	 * (2) we allow ones that were initially empty, but
-	 * forbid the ones that become empty;
+	 * (2) we allow ones that were initially empty, and
+	 *     just drop the ones that become empty
 	 *
-	 * (3) we allow both.
+	 * (3) we allow ones that were initially empty, but
+	 *     halt for the ones that become empty;
+	 *
+	 * (4) we allow both.
 	 */
 	if (!opts->allow_empty)
 		return 0; /* let "git commit" barf as necessary */
@@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,
 	originally_empty = is_original_commit_empty(commit);
 	if (originally_empty < 0)
 		return originally_empty;
-	if (!originally_empty)
-		return 0;
-	else
+	if (originally_empty)
 		return 1;
+	else if (opts->drop_redundant_commits)
+		return 2;
+	else
+		return 0;
 }
 
 static struct {
@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,
 	char *author = NULL;
 	struct commit_message msg = { NULL, NULL, NULL, NULL };
 	struct strbuf msgbuf = STRBUF_INIT;
-	int res, unborn = 0, reword = 0, allow;
+	int res, unborn = 0, reword = 0, allow, drop_commit;
 
 	if (opts->no_commit) {
 		/*
@@ -1935,13 +1946,20 @@ static int do_pick_commit(struct repository *r,
 		goto leave;
 	}
 
+	drop_commit = 0;
 	allow = allow_empty(r, opts, commit);
 	if (allow < 0) {
 		res = allow;
 		goto leave;
-	} else if (allow)
+	} else if (allow == 1) {
 		flags |= ALLOW_EMPTY;
-	if (!opts->no_commit) {
+	} else if (allow == 2) {
+		drop_commit = 1;
+		fprintf(stderr,
+			_("dropping %s %s -- patch contents already upstream\n"),
+			oid_to_hex(&commit->object.oid), msg.subject);
+	} /* else allow == 0 and there's nothing special to do */
+	if (!opts->no_commit && !drop_commit) {
 		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
 			res = do_commit(r, msg_file, author, opts, flags);
 		else
@@ -2495,6 +2513,12 @@ static int read_populate_opts(struct replay_opts *opts)
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
+		if (file_exists(rebase_path_drop_redundant_commits()))
+			opts->drop_redundant_commits = 1;
+
+		if (file_exists(rebase_path_keep_redundant_commits()))
+			opts->keep_redundant_commits = 1;
+
 		read_strategy_opts(opts, &buf);
 		strbuf_release(&buf);
 
@@ -2574,6 +2598,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
 	if (opts->signoff)
 		write_file(rebase_path_signoff(), "--signoff\n");
+	if (opts->drop_redundant_commits)
+		write_file(rebase_path_drop_redundant_commits(), "%s", "");
+	if (opts->keep_redundant_commits)
+		write_file(rebase_path_keep_redundant_commits(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
diff --git a/sequencer.h b/sequencer.h
index c165e0ff254..3b0ab9141fb 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -39,6 +39,7 @@ struct replay_opts {
 	int allow_rerere_auto;
 	int allow_empty;
 	int allow_empty_message;
+	int drop_redundant_commits;
 	int keep_redundant_commits;
 	int verbose;
 	int quiet;
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
index 3b716e980e7..cfb1ebc1ff9 100755
--- a/t/t3424-rebase-empty.sh
+++ b/t/t3424-rebase-empty.sh
@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '
 	git commit -m "Five letters ought to be enough for anybody"
 '
 
-test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
+test_expect_failure 'rebase (am-backend)' '
 	test_when_finished "git rebase --abort" &&
 	git checkout -B testing localmods &&
 	# rebase (--am) should not drop commits that start empty
@@ -45,10 +45,17 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
 	test_cmp expect actual
 '
 
-test_expect_failure 'rebase --merge with a variety of empty commits' '
-	test_when_finished "git rebase --abort" &&
+test_expect_success 'rebase --merge --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge uses default of --empty=drop' '
 	git checkout -B testing localmods &&
-	# rebase --merge should not halt on the commit that becomes empty
 	git rebase --merge upstream &&
 
 	test_write_lines D C B A >expect &&
@@ -56,7 +63,56 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '
 	test_cmp expect actual
 '
 
-test_expect_success 'rebase --interactive with a variety of empty commits' '
+test_expect_success 'rebase --merge --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --merge --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --merge --empty=ask upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=drop' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=drop upstream &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=keep' '
+	git checkout -B testing localmods &&
+	git rebase --interactive --empty=keep upstream &&
+
+	test_write_lines D C2 C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive --empty=ask' '
+	git checkout -B testing localmods &&
+	test_must_fail git rebase --interactive --empty=ask upstream &&
+
+	git rebase --skip &&
+
+	test_write_lines D C B A >expect &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'rebase --interactive uses default of --empty=ask' '
 	git checkout -B testing localmods &&
 	test_must_fail git rebase --interactive upstream &&
 
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
index 8dceef61cff..79e43a370ba 100755
--- a/t/t3427-rebase-subtree.sh
+++ b/t/t3427-rebase-subtree.sh
@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master &&
 	: first pick results in no changes &&
 	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' '
 	verbose test "$(commit_message HEAD)" = "Empty commit"
 '
 
-test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' '
 	reset_rebase &&
 	git checkout -b rebase-merges-onto to-rebase &&
-	test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
+	test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root &&
 	: first pick results in no changes &&
 	git rebase --skip &&
 	verbose test "$(commit_message HEAD~2)" = "master4" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 05/20] t3406: simplify an already simple test
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (3 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 04/20] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 06/20] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
                           ` (15 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

When the merge backend was re-implemented on top of the interactive
backend, the output of rebase --merge changed a little.  This change
allowed this test to be simplified, though it wasn't noticed until now.
Simplify the testcase a little.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3406-rebase-message.sh | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index b393e1e9fee..0c2c569f959 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -18,11 +18,8 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'rebase -m' '
-	git rebase -m master >report &&
-	>expect &&
-	sed -n -e "/^Already applied: /p" \
-		-e "/^Committed: /p" report >actual &&
-	test_cmp expect actual
+	git rebase -m master >actual &&
+	test_must_be_empty actual
 '
 
 test_expect_success 'rebase against master twice' '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 06/20] rebase, sequencer: remove the broken GIT_QUIET handling
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (4 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 05/20] t3406: simplify an already simple test Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 07/20] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
                           ` (14 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

The GIT_QUIET environment variable was used to signal the non-am
backends that the rebase should perform quietly.  The preserve-merges
backend does not make use of the quiet flag anywhere (other than to
write out its state whenever it writes state), and this mechanism was
broken in the conversion from shell to C.  Since this environment
variable was specifically designed for scripts and the only backend that
would still use it is no longer a script, just gut this code.

A subsequent commit will fix --quiet for the interactive/merge backend
in a different way.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 6 ++----
 sequencer.c      | 6 ++----
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 669690f9664..7551f950593 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		   opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
 	write_file(state_dir_path("orig-head", opts), "%s",
 		   oid_to_hex(&opts->orig_head));
-	write_file(state_dir_path("quiet", opts), "%s",
-		   opts->flags & REBASE_NO_QUIET ? "" : "t");
+	if (!(opts->flags & REBASE_NO_QUIET))
+		write_file(state_dir_path("quiet", opts), "%s", "");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
 	add_var(&script_snippet, "git_am_opt", buf.buf);
 	strbuf_release(&buf);
diff --git a/sequencer.c b/sequencer.c
index fdb8f91fbce..f475d2a3b1c 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2570,8 +2570,6 @@ static void write_strategy_opts(struct replay_opts *opts)
 int write_basic_state(struct replay_opts *opts, const char *head_name,
 		      struct commit *onto, const char *orig_head)
 {
-	const char *quiet = getenv("GIT_QUIET");
-
 	if (head_name)
 		write_file(rebase_path_head_name(), "%s\n", head_name);
 	if (onto)
@@ -2580,8 +2578,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 	if (orig_head)
 		write_file(rebase_path_orig_head(), "%s\n", orig_head);
 
-	if (quiet)
-		write_file(rebase_path_quiet(), "%s\n", quiet);
+	if (opts->quiet)
+		write_file(rebase_path_quiet(), "%s", "");
 	if (opts->verbose)
 		write_file(rebase_path_verbose(), "%s", "");
 	if (opts->strategy)
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 07/20] rebase: make sure to pass along the quiet flag to the sequencer
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (5 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 06/20] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 08/20] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
                           ` (13 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  | 3 ++-
 t/t3400-rebase.sh | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 7551f950593..1a664137d29 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.allow_empty_message = opts->allow_empty_message;
 	replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
 	replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+	replay.quiet = !(opts->flags & REBASE_NO_QUIET);
 	replay.verbose = opts->flags & REBASE_VERBOSE;
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			 N_("allow pre-rebase hook to run")),
 		OPT_NEGBIT('q', "quiet", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+			   REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
 		OPT_BIT('v', "verbose", &options.flags,
 			N_("display a diffstat of what changed upstream"),
 			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 221b35f2df3..79762b989a4 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase -q is quiet' '
+test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
 	git rebase -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
+test_expect_success 'rebase --merge -q is quiet' '
+	git checkout -B quiet topic &&
+	git rebase --merge -q master >output.out 2>&1 &&
+	test_must_be_empty output.out
+'
+
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	(
 		echo "One" &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 08/20] rebase: fix handling of restrict_revision
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (6 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 07/20] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 09/20] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
                           ` (12 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

restrict_revision in the original shell script was an excluded revision
range.  It is also treated that way by the am-backend.  In the
conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the
interactive-backend accidentally treated it as a positive revision
rather than a negated one.

This was missed as there were no tests in the testsuite that tested an
interactive rebase with fork-point behavior.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c  |  4 ++--
 t/t3400-rebase.sh | 20 +++++++++++++++++++-
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 1a664137d29..8264a9243f1 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 
 	argv_array_pushl(&make_script_args, "", revisions, NULL);
 	if (opts->restrict_revision)
-		argv_array_push(&make_script_args,
-				oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_pushf(&make_script_args, "^%s",
+				 oid_to_hex(&opts->restrict_revision->object.oid));
 
 	ret = sequencer_make_script(the_repository, &todo_list.buf,
 				    make_script_args.argc, make_script_args.argv,
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 79762b989a4..71fd6396cdc 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' '
 	git rebase master
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' '
 	git checkout -b default-base master &&
 	git checkout -b default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual &&
+	git checkout default-base &&
+	git reset --hard HEAD^ &&
+	git checkout default &&
+	git rebase --merge &&
+	git rev-parse --verify default-base >expect &&
+	git rev-parse default~1 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+	git checkout -B default-base master &&
+	git checkout -B default topic &&
+	git config branch.default.remote . &&
+	git config branch.default.merge refs/heads/default-base &&
 	git rebase &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 09/20] t3432: make these tests work with either am or merge backends
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (7 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 08/20] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 10/20] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
                           ` (11 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

t3432 had several stress tests for can_fast_forward(), whose intent was
to ensure we were using the optimization of just fast forwarding when
possible.  However, these tests verified that fast forwards had happened
based on the output that rebase printed to the terminal.  We can instead
test more directly that we actually fast-forwarded by checking the
reflog, which also has the side effect of making the tests applicable
for the merge/interactive backend.

This change does lose the distinction between "noop" and "noop-force",
but as stated in commit c9efc216830f ("t3432: test for --no-ff's
interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo,
just test for what we're currently doing.".

This change does not actually run these tests with the merge/interactive
backend; instead this is just a preparatory commit.  A subsequent commit
which fixes can_fast_forward() to work with that backend will then also
change t3432 to add tests of that backend as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 92f95b57da1..7432c0e2415 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -44,19 +44,15 @@ test_rebase_same_head_ () {
 	test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
 		oldhead=\$(git rev-parse HEAD) &&
 		test_when_finished 'git reset --hard \$oldhead' &&
+		cp .git/logs/HEAD expect &&
 		git rebase$flag $* >stdout &&
 		if test $what = work
 		then
-			# Must check this case first, for 'is up to
-			# date, rebase forced[...]rewinding head' cases
-			test_i18ngrep 'rewinding head' stdout
+			old=\$(wc -l <expect) &&
+			test_line_count '-gt' \$old .git/logs/HEAD
 		elif test $what = noop
 		then
-			test_i18ngrep 'is up to date' stdout &&
-			test_i18ngrep ! 'rebase forced' stdout
-		elif test $what = noop-force
-		then
-			test_i18ngrep 'is up to date, rebase forced' stdout
+			test_cmp expect .git/logs/HEAD
 		fi &&
 		newhead=\$(git rev-parse HEAD) &&
 		if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () {
 
 changes='no changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' '
 
 changes='our changes'
 test_rebase_same_head success noop same success work same
-test_rebase_same_head success noop same success noop-force same master
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
-test_rebase_same_head success noop same success noop-force same --onto master... master
-test_rebase_same_head success noop same success noop-force same --keep-base master
-test_rebase_same_head success noop same success noop-force same --keep-base
-test_rebase_same_head success noop same success noop-force same --no-fork-point
-test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point
+test_rebase_same_head success noop same success work same master
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
+test_rebase_same_head success noop same success work same --onto master... master
+test_rebase_same_head success noop same success work same --keep-base master
+test_rebase_same_head success noop same success work same --keep-base
+test_rebase_same_head success noop same success work same --no-fork-point
+test_rebase_same_head success noop same success work same --keep-base --no-fork-point
 test_rebase_same_head success noop same success work same --fork-point master
 test_rebase_same_head success noop same success work diff --fork-point --onto B B
 test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' '
 '
 
 changes='our and their changes'
-test_rebase_same_head success noop same success noop-force diff --onto B B
-test_rebase_same_head success noop same success noop-force diff --onto B... B
+test_rebase_same_head success noop same success work diff --onto B B
+test_rebase_same_head success noop same success work diff --onto B... B
 test_rebase_same_head success noop same success work diff --onto master... master
 test_rebase_same_head success noop same success work diff --keep-base master
 test_rebase_same_head success noop same success work diff --keep-base
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 10/20] rebase: allow more types of rebases to fast-forward
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (8 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 09/20] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 11/20] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
                           ` (10 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we dis-allowed rebases using the interactive backend from
performing a fast-forward to short-circuit the rebase operation.  This
made sense for explicitly interactive rebases and some implicitly
interactive rebases, but certainly became overly stringent when the
merge backend was re-implemented via the interactive backend.

Just as the am-based rebase has always had to disable the fast-forward
based on a variety of conditions or flags (e.g. --signoff, --whitespace,
etc.), we need to do the same but now with a few more options.  However,
continuing to use REBASE_FORCE for tracking this is problematic because
the interactive backend used it for a different purpose.  (When
REBASE_FORCE wasn't set, the interactive backend would not fast-forward
the whole series but would fast-forward individual "pick" commits at the
beginning of the todo list, and then a squash or something would cause
it to start generating new commits.)  So, introduce a new
allow_preemptive_ff flag contained within cmd_rebase() and use it to
track whether we are going to allow a pre-emptive fast-forward that
short-circuits the whole rebase.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c               | 18 ++++++++++++++----
 t/t3432-rebase-fast-forward.sh |  2 ++
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 8264a9243f1..6e9a2fedc7f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct object_id squash_onto;
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
+	int allow_preemptive_ff = 1;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		    state_dir_base, cmd_live_rebase, buf.buf);
 	}
 
+	if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+	    (action != ACTION_NONE) ||
+	    (exec.nr > 0) ||
+	    options.autosquash) {
+		allow_preemptive_ff = 0;
+	}
+
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
 		if (!strcmp(option, "--committer-date-is-author-date") ||
 		    !strcmp(option, "--ignore-date") ||
 		    !strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
-			options.flags |= REBASE_FORCE;
+			allow_preemptive_ff = 0;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	/*
 	 * Check if we are already based on onto with linear history,
 	 * in which case we could fast-forward without replacing the commits
-	 * with new commits recreated by replaying their changes. This
-	 * optimization must not be done if this is an interactive rebase.
+	 * with new commits recreated by replaying their changes.
+	 *
+	 * Note that can_fast_forward() initializes merge_base, so we have to
+	 * call it before checking allow_preemptive_ff.
 	 */
 	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
 		    &options.orig_head, &merge_base) &&
-	    !is_interactive(&options)) {
+	    allow_preemptive_ff) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 7432c0e2415..40388ccf9f3 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -30,6 +30,8 @@ test_rebase_same_head () {
 	shift &&
 	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
 
 test_rebase_same_head_ () {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 11/20] git-rebase.txt: add more details about behavioral differences of backends
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (9 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 10/20] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 12/20] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
                           ` (9 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++---
 1 file changed, 80 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index e1c6f918013..6e2569cd8a7 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -416,7 +416,7 @@ with `--keep-base` in order to drop those commits from your branch.
 
 --ignore-whitespace::
 --whitespace=<option>::
-	These flag are passed to the 'git apply' program
+	These flags are passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
 +
 See also INCOMPATIBLE OPTIONS below.
@@ -563,8 +563,8 @@ The following options:
 
  * --committer-date-is-author-date
  * --ignore-date
- * --whitespace
  * --ignore-whitespace
+ * --whitespace
  * -C
 
 are incompatible with the following options:
@@ -615,9 +615,84 @@ handling commits that become empty.
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Directory rename heuristics are enabled in the merge and interactive
-backends.  Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the am backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge and interactive
+backends to provide you warnings in such cases.
+
+Context
+~~~~~~~
+
+The am backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally).  Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes.  The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file.  The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines.  However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked.  There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The interactive backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from.  Since the am backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary.  Also, when merge.conflictStyle is
+set to diff3, the am backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The interactive backend works with the full commits on both sides of
+history and thus has no such limitations.
+
+Hooks
+~~~~~
+
+The am backend has not traditionally called the post-commit hook,
+while the merge/interactive backend has.  However, this was by
+accident of implementation rather than by design.  Both backends
+should have the same behavior, though it is not clear which one is
+correct.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+  the changes made in the reflog, though both will make use of the
+  word "rebase".
+
+* Progress, informational, and error messages: The two backends
+  provide slightly different progress and informational messages.
+  Also, the am backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the interactive backend
+  writes them to stderr.
+
+* State directories: The two backends keep their state in different
+  directories under .git/
 
 include::merge-strategies.txt[]
 
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 12/20] rebase: move incompatibility checks between backend options a bit earlier
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (10 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 11/20] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 13/20] rebase: add an --am option Elijah Newren via GitGitGadget
                           ` (8 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 6e9a2fedc7f..a2f05f783d8 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
+	if (options.git_am_opts.argc) {
+		/* all am options except -q are compatible only with --am */
+		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+			if (strcmp(options.git_am_opts.argv[i], "-q"))
+				break;
+
+		if (is_interactive(&options) && i >= 0)
+			die(_("cannot combine am options with either "
+			      "interactive or merge options"));
+	}
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (reschedule_failed_exec >= 0)
 		options.reschedule_failed_exec = reschedule_failed_exec;
 
-	if (options.git_am_opts.argc) {
-		/* all am options except -q are compatible only with --am */
-		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
-			if (strcmp(options.git_am_opts.argv[i], "-q"))
-				break;
-
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
-	}
-
 	if (options.signoff) {
 		if (options.type == REBASE_PRESERVE_MERGES)
 			die("cannot combine '--signoff' with "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 13/20] rebase: add an --am option
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (11 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 12/20] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 14/20] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
                           ` (7 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Currently, this option doesn't do anything except error out if any
options requiring the interactive-backend are also passed.  However,
when we make the default backend configurable later in this series, this
flag will provide a way to override the config setting.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt | 11 ++++++++++-
 builtin/rebase.c             | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 6e2569cd8a7..93092da3bf6 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
+--am:
+	Use git-am internally to rebase.  This option may become a
+	no-op in the future once the interactive backend handles
+	everything the am one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
 --empty={drop,keep,ask}::
 	How to handle commits that are not empty to start and are not
 	clean cherry-picks of any upstream commit, but which become
@@ -378,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.
+	ever ignored.  Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -418,6 +425,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	These flags are passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
+	Implies --am.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -561,6 +569,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
+ * --am
  * --committer-date-is-author-date
  * --ignore-date
  * --ignore-whitespace
diff --git a/builtin/rebase.c b/builtin/rebase.c
index a2f05f783d8..77abe674cb9 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,
 	return res && is_linear_history(onto, head);
 }
 
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+	struct rebase_options *opts = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	opts->type = REBASE_AM;
+
+	return 0;
+}
+
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
+		{ OPTION_CALLBACK, 0, "am", &options, NULL,
+			N_("use apply-mail strategies to rebase"),
+			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
 			N_("use merging strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc) {
+	if (options.git_am_opts.argc || options.type == REBASE_AM) {
 		/* all am options except -q are compatible only with --am */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 14/20] git-prompt: change the prompt for interactive-based rebases
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (12 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 13/20] rebase: add an --am option Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 15/20] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
                           ` (6 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In the past, we had different prompts for different types of rebases:
   REBASE: for am-based rebases
   REBASE-m: for merge-based rebases
   REBASE-i: for interactive-based rebases

It's not clear why this distinction was necessary or helpful; when the
prompt was added in commit e75201963f67 ("Improve bash prompt to detect
various states like an unfinished merge", 2007-09-30), it simply added
these three different types.  Perhaps there was a useful purpose back
then, but there have been some changes:

  * The merge backend was deleted after being implemented on top of the
    interactive backend, causing the prompt for merge-based rebases to
    change from REBASE-m to REBASE-i.
  * The interactive backend is used for multiple different types of
    non-interactive rebases, so the "-i" part of the prompt doesn't
    really mean what it used to.
  * Rebase backends have gained more abilities and have a great deal of
    overlap, sometimes making it hard to distinguish them.
  * Behavioral differences between the backends have also been ironed
    out.
  * We want to change the default backend from am to interactive, which
    means people would get "REBASE-i" by default if we didn't change
    the prompt, and only if they specified --am or --whitespace or -C
    would they get the "REBASE" prompt.
  * In the future, we plan to have "--whitespace", "-C", and even "--am"
    run the interactive backend once it can handle everything the
    am-backend can.

For all these reasons, make the prompt for any type of rebase just be
"REBASE".

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 contrib/completion/git-prompt.sh | 6 +-----
 t/t9903-bash-prompt.sh           | 8 ++++----
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd47be..014cd7c3cfc 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -429,11 +429,7 @@ __git_ps1 ()
 		__git_eread "$g/rebase-merge/head-name" b
 		__git_eread "$g/rebase-merge/msgnum" step
 		__git_eread "$g/rebase-merge/end" total
-		if [ -f "$g/rebase-merge/interactive" ]; then
-			r="|REBASE-i"
-		else
-			r="|REBASE-m"
-		fi
+		r="|REBASE"
 	else
 		if [ -d "$g/rebase-apply" ]; then
 			__git_eread "$g/rebase-apply/next" step
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 88bc733ad69..7ca35d358d6 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' '
 '
 
 test_expect_success 'prompt - interactive rebase' '
-	printf " (b1|REBASE-i 2/3)" >expected &&
+	printf " (b1|REBASE 2/3)" >expected &&
 	write_script fake_editor.sh <<-\EOF &&
 		echo "exec echo" >"$1"
 		echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' '
 '
 
 test_expect_success 'prompt - rebase merge' '
-	printf " (b2|REBASE-i 1/3)" >expected &&
+	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
 	test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' '
 	test_cmp expected "$actual"
 '
 
-test_expect_success 'prompt - rebase' '
+test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase b1 b2 &&
+	test_must_fail git rebase --am b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 15/20] rebase: drop '-i' from the reflog for interactive-based rebases
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (13 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 14/20] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 16/20] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
                           ` (5 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

A large variety of rebase types are supported by the interactive
machinery, not just the explicitly interactive ones.  These all share
the same code and write the same reflog messages, but the "-i" moniker
in those messages doesn't really have much meaning.  It also becomes
somewhat distracting once we switch the default from the am-backend to
the interactive one.  Just remove the "-i" from these messages.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 builtin/rebase.c              |  2 +-
 sequencer.c                   |  8 ++++----
 t/t3404-rebase-interactive.sh | 10 +++++-----
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 77abe674cb9..ffaa7935240 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)
 	if (env && strcmp("rebase", env))
 		return; /* only override it if it is "rebase" */
 
-	strbuf_addf(&buf, "rebase -i (%s)", options->action);
+	strbuf_addf(&buf, "rebase (%s)", options->action);
 	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
 	strbuf_release(&buf);
 }
diff --git a/sequencer.c b/sequencer.c
index f475d2a3b1c..f8e242b6695 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)
 			char *eol = strchr(p, '\n');
 			if (eol)
 				*eol = '\0';
-			if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) {
+			if (delete_ref("(rebase) cleanup", p, NULL, 0) < 0) {
 				warning(_("could not delete '%s'"), p);
 				ret = -1;
 			}
@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)
 	case REPLAY_PICK:
 		return N_("cherry-pick");
 	case REPLAY_INTERACTIVE_REBASE:
-		return N_("rebase -i");
+		return N_("rebase");
 	}
 	die(_("unknown action: %d"), opts->action);
 }
@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r,
 			       COMMIT_LOCK | SKIP_IF_UNCHANGED))
 		/*
 		 * TRANSLATORS: %s will be "revert", "cherry-pick" or
-		 * "rebase -i".
+		 * "rebase".
 		 */
 		return error(_("%s: Unable to write new index file"),
 			_(action_name(opts)));
@@ -3199,7 +3199,7 @@ static int do_label(struct repository *r, const char *name, int len)
 		return error(_("illegal label name: '%.*s'"), len, name);
 
 	strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
-	strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
+	strbuf_addf(&msg, "rebase (label) '%.*s'", len, name);
 
 	transaction = ref_store_transaction_begin(refs, &err);
 	if (!transaction) {
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index c41531f3490..a31583eb2fd 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' '
 '
 
 test_expect_success 'reflog for the branch shows correct finish message' '
-	printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \
+	printf "rebase (finish): refs/heads/branch1 onto %s\n" \
 		"$(git rev-parse branch2)" >expected &&
 	git log -g --pretty=%gs -1 refs/heads/branch1 >actual &&
 	test_cmp expected actual
@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' '
 	git branch -f branch-reflog-test H &&
 	git rebase -i --onto I F branch-reflog-test &&
 	cat >expect <<-\EOF &&
-	rebase -i (finish): returning to refs/heads/branch-reflog-test
-	rebase -i (pick): H
-	rebase -i (pick): G
-	rebase -i (start): checkout I
+	rebase (finish): returning to refs/heads/branch-reflog-test
+	rebase (pick): H
+	rebase (pick): G
+	rebase (start): checkout I
 	EOF
 	git reflog -n4 HEAD |
 	sed "s/[^:]*: //" >actual &&
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 16/20] rebase tests: mark tests specific to the am-backend with --am
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (14 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 15/20] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 17/20] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
                           ` (4 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

We have many rebase tests in the testsuite, and often the same test is
repeated multiple times just testing different backends.  For those
tests that were specifically trying to test the am backend, add the --am
flag.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t3400-rebase.sh                 | 10 ++++-----
 t/t3401-rebase-and-am-rename.sh   |  4 ++--
 t/t3404-rebase-interactive.sh     |  2 +-
 t/t3406-rebase-message.sh         | 12 +++++------
 t/t3407-rebase-abort.sh           |  6 +++---
 t/t3420-rebase-autostash.sh       |  2 +-
 t/t3421-rebase-topology-linear.sh | 34 +++++++++++++++----------------
 t/t3425-rebase-topology-merges.sh |  8 ++++----
 t/t3432-rebase-fast-forward.sh    |  4 ++--
 t/t5407-post-rewrite-hook.sh      | 12 +++++------
 t/t7512-status-help.sh            | 12 +++++------
 11 files changed, 53 insertions(+), 53 deletions(-)

diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 71fd6396cdc..0a491f23632 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase &&
+	git rebase --am &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 
 test_expect_success 'rebase --am -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase -q master >output.out 2>&1 &&
+	git rebase --am -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase -f --onto init HEAD^ &&
+		test_must_fail git rebase --am -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index a0b9438b228..50803958fd0 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase (am): directory rename detected' '
+test_expect_failure 'rebase --am: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase A &&
+		git -c merge.directoryRenames=true rebase --am A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index a31583eb2fd..f964b2cd41d 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 0c2c569f959..7ce617fc1fb 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase master >out &&
+	git rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase master >out &&
+	git rebase --force-rebase --am master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase master topic >out &&
+	git rebase --am master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase topic >out &&
+	git rebase --am topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase reflog-onto &&
+	git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 910f2182843..3e318261703 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --quit' '
+test_expect_success 'rebase --am --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase master &&
+	test_must_fail git rebase --am master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 5f7e73cf83a..3816159e207 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase "" .git/rebase-apply
+testrebase " --am" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 57334dca7e4..e12304012a2 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -26,7 +26,7 @@ test_run_rebase () {
 		test_linear_range 'd e' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -50,7 +50,7 @@ test_run_rebase () {
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -66,7 +66,7 @@ test_run_rebase () {
 		test_linear_range 'd e' b..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -83,7 +83,7 @@ test_run_rebase () {
 		test_linear_range 'd e' branch-b..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -98,7 +98,7 @@ test_run_rebase () {
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -139,7 +139,7 @@ test_run_rebase () {
 		test_linear_range 'd i' h..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -154,7 +154,7 @@ test_run_rebase () {
 		test_linear_range 'd' h..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -169,7 +169,7 @@ test_run_rebase () {
 		test_linear_range 'd i' f..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -184,7 +184,7 @@ test_run_rebase () {
 		test_linear_range 'd gp i' h..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -212,7 +212,7 @@ test_run_rebase () {
 		test_linear_range 'j d k l' c..
 	"
 }
-test_run_rebase failure ''
+test_run_rebase failure --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -227,7 +227,7 @@ test_run_rebase () {
 		test_linear_range 'd k l' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -242,7 +242,7 @@ test_run_rebase () {
 		test_linear_range 'd k l' j..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -282,7 +282,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -297,7 +297,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -312,7 +312,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -328,7 +328,7 @@ test_run_rebase () {
 	"
 }
 
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -343,7 +343,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index fd8efe84fe8..19700b025b5 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success ''
+test_run_rebase success --am
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' ''
+test_run_rebase success 'n o e' --am
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' ''
+test_run_rebase success 'd n o e' --am
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 40388ccf9f3..4b3cecce560 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 7344253bfbb..a8a73616e46 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase' '
+test_expect_success 'git rebase --am' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip' '
+test_expect_success 'git rebase --am --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --am --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip the last one' '
+test_expect_success 'git rebase --am --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --onto D A &&
+	test_must_fail git rebase --am --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 66d7a627972..d22b0acf2ac 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase in progress before resolving conflicts' '
+test_expect_success 'status when rebase --am in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase in progress before rebase --continue' '
+test_expect_success 'status when rebase --am in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase conflicts with statushints disabled' '
+test_expect_success 'status when rebase --am conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' '
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 17/20] rebase tests: repeat some tests using the merge backend instead of am
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (15 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 16/20] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 18/20] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
                           ` (3 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

In order to ensure the merge/interactive backend gets similar coverage
to the am one, add some tests for cases where previously only the am
backend was tested.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 t/t5520-pull.sh                   | 17 +++++++++++++++--
 t/t6047-diff3-conflict-markers.sh | 13 +++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 602d996a33b..3fff6a06fa8 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -277,14 +277,27 @@ test_expect_success '--rebase' '
 	test_cmp expect actual
 '
 
-test_expect_success '--rebase fast forward' '
+test_expect_success '--rebase (merge) fast forward' '
 	git reset --hard before-rebase &&
 	git checkout -b ff &&
 	echo another modification >file &&
 	git commit -m third file &&
 
 	git checkout to-rebase &&
-	git pull --rebase . ff &&
+	git -c rebase.backend=merge pull --rebase . ff &&
+	test_cmp_rev HEAD ff &&
+
+	# The above only validates the result.  Did we actually bypass rebase?
+	git reflog -1 >reflog.actual &&
+	sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
+	echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected &&
+	test_cmp reflog.expected reflog.fuzzy
+'
+
+test_expect_success '--rebase (am) fast forward' '
+	git reset --hard before-rebase &&
+
+	git -c rebase.backend=am pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index 860542aad00..d383ce81302 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' '
 	)
 '
 
-test_expect_success 'rebase describes fake ancestor base' '
+test_expect_success 'rebase --merge describes parent of commit being picked' '
 	test_create_repo rebase &&
 	(
 		cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' '
 		test_commit master file &&
 		git checkout -b side HEAD^ &&
 		test_commit side file &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master &&
+		grep "||||||| parent of" file
+	)
+'
+
+test_expect_success 'rebase --am describes fake ancestor base' '
+	(
+		cd rebase &&
+		git rebase --abort &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
 		grep "||||||| constructed merge base" file
 	)
 '
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 18/20] rebase: make the backend configurable via config setting
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (16 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 17/20] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 19/20] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
                           ` (2 subsequent siblings)
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt |  8 ++++++++
 builtin/rebase.c                | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d812e..e6ae30c9999 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,14 @@ rebase.useBuiltin::
 	is always used. Setting this will emit a warning, to alert any
 	remaining users that setting this now does nothing.
 
+rebase.backend::
+	Default backend to use for rebasing.  Possible choices are
+	'am' or 'merge' (note that the merge backend is sometimes also
+	refered to as the interactive backend or the interactive
+	machinery elsewhere in the docs).  Also, in the future, if the
+	merge backend gains all remaining capabilities of the am
+	backend, this setting may become unused.
+
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
 	rebase. False by default.
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ffaa7935240..f0a862f41b5 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -60,6 +60,7 @@ enum empty_type {
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
+	const char *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -100,6 +101,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
+		.default_backend = "am",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)
 		return 0;
 	}
 
+	if (!strcmp(var, "rebase.backend")) {
+		return git_config_string(&opts->default_backend, var, value);
+	}
+
 	return git_default_config(var, value, data);
 }
 
@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
-		if (is_interactive(&options) && i >= 0)
-			die(_("cannot combine am options with either "
-			      "interactive or merge options"));
+		if (i >= 0) {
+			if (is_interactive(&options))
+				die(_("cannot combine am options with either "
+				      "interactive or merge options"));
+			else
+				options.type = REBASE_AM;
+		}
+	}
+
+	if (options.type == REBASE_UNSPECIFIED) {
+		if (!strcmp(options.default_backend, "merge"))
+			options.type = REBASE_MERGE;
+		else if (!strcmp(options.default_backend, "am"))
+			options.type = REBASE_AM;
+		else
+			die(_("Unknown rebase backend: %s"),
+			    options.default_backend);
 	}
 
 	switch (options.type) {
@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		options.state_dir = apply_dir();
 		break;
 	default:
-		/* the default rebase backend is `--am` */
-		options.type = REBASE_AM;
-		options.state_dir = apply_dir();
-		break;
+		BUG("options.type was just set above; should be unreachable.");
 	}
 
 	if (options.empty == EMPTY_UNSPECIFIED) {
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 19/20] rebase: change the default backend from "am" to "merge"
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (17 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 18/20] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-02-15 21:36         ` [PATCH v5 20/20] rebase: rename the two primary rebase backends Elijah Newren via GitGitGadget
  2020-02-16 15:01         ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Phillip Wood
  20 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

The am-backend drops information and thus limits what we can do:

  * lack of full tree information from the original commits means we
    cannot do directory rename detection and warn users that they might
    want to move some of their new files that they placed in old
    directories to prevent their becoming orphaned.[1]
  * reduction in context from only having a few lines beyond those
    changed means that when context lines are non-unique we can apply
    patches incorrectly.[2]
  * lack of access to original commits means that conflict marker
    annotation has less information available.
  * the am backend has safety problems with an ill-timed interrupt.

Also, the merge/interactive backend have far more abilities, appear to
currently have a slight performance advantage[3] and have room for more
optimizations than the am backend[4] (and work is underway to take
advantage of some of those possibilities).

[1] https://lore.kernel.org/git/xmqqh8jeh1id.fsf@gitster-ct.c.googlers.com/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/git-rebase.txt           | 13 ++++++++++++-
 builtin/rebase.c                       |  4 ++--
 t/t5520-pull.sh                        | 10 ++++++----
 t/t9106-git-svn-commit-diff-clobber.sh |  3 ++-
 4 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 93092da3bf6..fbac1cf38dd 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -315,7 +315,7 @@ See also INCOMPATIBLE OPTIONS below.
 --merge::
 	Use merging strategies to rebase.  When the recursive (default) merge
 	strategy is used, this allows rebase to be aware of renames on the
-	upstream side.
+	upstream side.  This is the default.
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
@@ -683,6 +683,17 @@ accident of implementation rather than by design.  Both backends
 should have the same behavior, though it is not clear which one is
 correct.
 
+Interruptability
+~~~~~~~~~~~~~~~~
+
+The am backend has safety problems with an ill-timed interrupt; if the
+user presses Ctrl-C at the wrong time to try to abort the rebase, the
+rebase can enter a state where it cannot be aborted with a subsequent
+`git rebase --abort`.  The interactive backend does not appear to
+suffer from the same shortcoming.  (See
+https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for
+details.)
+
 Miscellaneous differences
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index f0a862f41b5..db8fd699d79 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -101,7 +101,7 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "am",	  	\
+		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			options.type = REBASE_MERGE;
+			imply_interactive(&options, "--merge");
 		else if (!strcmp(options.default_backend, "am"))
 			options.type = REBASE_AM;
 		else
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 3fff6a06fa8..4f9e7f7ff6b 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' '
 	test_tick &&
 	git commit -m "Create conflict" seq.txt &&
 	test_must_fail git pull --rebase . seq 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' '
 	git checkout -f -b fails-to-rebase HEAD^ &&
 	test_commit v2-without-cr file "2" file2-lf &&
 	test_must_fail git pull --rebase . diverging 2>err >out &&
-	test_i18ngrep "Resolve all conflicts manually" out
+	test_i18ngrep "Resolve all conflicts manually" err
 '
 
 test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' '
 	(
 		cd dst &&
 		test_must_fail git pull --rebase &&
-		find .git/rebase-apply -name "000*" >patches &&
-		test_line_count = 1 patches
+		cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work &&
+		grep -v -e \# -e ^$ work >patches &&
+		test_line_count = 1 patches &&
+		rm -f work
 	)
 '
 
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index dbe8deac0d2..aec45bca3b7 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' "
 
 
 test_expect_success 'check that rebase really failed' '
-	test -d .git/rebase-apply
+	git status >output &&
+	grep currently.rebasing output
 '
 
 test_expect_success 'resolve, continue the rebase and dcommit' "
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (18 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 19/20] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
@ 2020-02-15 21:36         ` Elijah Newren via GitGitGadget
  2020-03-12 15:13           ` Emily Shaffer
  2020-02-16 15:01         ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Phillip Wood
  20 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren via GitGitGadget @ 2020-02-15 21:36 UTC (permalink / raw)
  To: git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren,
	Elijah Newren

From: Elijah Newren <newren@gmail.com>

Two related changes, with separate rationale for each:

Rename the 'interactive' backend to 'merge' because:
  * 'interactive' as a name caused confusion; this backend has been used
    for many kinds of non-interactive rebases, and will probably be used
    in the future for more non-interactive rebases than interactive ones
    given that we are making it the default.
  * 'interactive' is not the underlying strategy; merging is.
  * the directory where state is stored is not called
    .git/rebase-interactive but .git/rebase-merge.

Rename the 'am' backend to 'apply' because:
  * Few users are familiar with git-am as a reference point.
  * Related to the above, the name 'am' makes sentences in the
    documentation harder for users to read and comprehend (they may read
    it as the verb from "I am"); avoiding this difficult places a large
    burden on anyone writing documentation about this backend to be very
    careful with quoting and sentence structure and often forces
    annoying redundancy to try to avoid such problems.
  * Users stumble over pronunciation ("am" as in "I am a person not a
    backend" or "am" as in "the first and thirteenth letters in the
    alphabet in order are "A-M"); this may drive confusion when one user
    tries to explain to another what they are doing.
  * While "am" is the tool driving this backend, the tool driving git-am
    is git-apply, and since we are driving towards lower-level tools
    for the naming of the merge backend we may as well do so here too.
  * The directory where state is stored has never been called
    .git/rebase-am, it was always called .git/rebase-apply.

For all the reasons listed above:
  * Modify the documentation to refer to the backends with the new names
  * Provide a brief note in the documentation connecting the new names
    to the old names in case users run across the old names anywhere
    (e.g. in old release notes or older versions of the documentation)
  * Change the (new) --am command line flag to --apply
  * Rename some enums, variables, and functions to reinforce the new
    backend names for us as well.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 Documentation/config/rebase.txt   |  8 +--
 Documentation/git-rebase.txt      | 77 +++++++++++++-----------
 builtin/rebase.c                  | 99 +++++++++++++++----------------
 t/t3400-rebase.sh                 | 16 ++---
 t/t3401-rebase-and-am-rename.sh   |  4 +-
 t/t3404-rebase-interactive.sh     |  2 +-
 t/t3406-rebase-message.sh         | 12 ++--
 t/t3407-rebase-abort.sh           |  6 +-
 t/t3420-rebase-autostash.sh       | 22 +++----
 t/t3421-rebase-topology-linear.sh | 34 +++++------
 t/t3424-rebase-empty.sh           |  6 +-
 t/t3425-rebase-topology-merges.sh |  8 +--
 t/t3432-rebase-fast-forward.sh    |  4 +-
 t/t5407-post-rewrite-hook.sh      | 16 ++---
 t/t5520-pull.sh                   |  2 +-
 t/t6047-diff3-conflict-markers.sh |  4 +-
 t/t7512-status-help.sh            | 12 ++--
 t/t9903-bash-prompt.sh            |  2 +-
 18 files changed, 166 insertions(+), 168 deletions(-)

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index e6ae30c9999..7f7a07d22f8 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -7,11 +7,9 @@ rebase.useBuiltin::
 
 rebase.backend::
 	Default backend to use for rebasing.  Possible choices are
-	'am' or 'merge' (note that the merge backend is sometimes also
-	refered to as the interactive backend or the interactive
-	machinery elsewhere in the docs).  Also, in the future, if the
-	merge backend gains all remaining capabilities of the am
-	backend, this setting may become unused.
+	'apply' or 'merge'.  In the future, if the merge backend gains
+	all remaining capabilities of the apply backend, this setting
+	may become unused.
 
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index fbac1cf38dd..8c1f4b82680 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,10 +258,10 @@ See also INCOMPATIBLE OPTIONS below.
 	original branch. The index and working tree are also left
 	unchanged as a result.
 
---am:
-	Use git-am internally to rebase.  This option may become a
-	no-op in the future once the interactive backend handles
-	everything the am one does.
+--apply:
+	Use applying strategies to rebase (calling `git-am`
+	internally).  This option may become a no-op in the future
+	once the merge backend handles everything the apply one does.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -385,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below.
 	Ensure at least <n> lines of surrounding context match before
 	and after each change.  When fewer lines of surrounding
 	context exist they all must match.  By default no context is
-	ever ignored.  Implies --am.
+	ever ignored.  Implies --apply.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -425,7 +425,7 @@ with `--keep-base` in order to drop those commits from your branch.
 --whitespace=<option>::
 	These flags are passed to the 'git apply' program
 	(see linkgit:git-apply[1]) that applies the patch.
-	Implies --am.
+	Implies --apply.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -569,7 +569,7 @@ INCOMPATIBLE OPTIONS
 
 The following options:
 
- * --am
+ * --apply
  * --committer-date-is-author-date
  * --ignore-date
  * --ignore-whitespace
@@ -604,42 +604,48 @@ In addition, the following pairs of options are incompatible:
 BEHAVIORAL DIFFERENCES
 -----------------------
 
-There are some subtle differences how the backends behave.
+git rebase has two primary backends: apply and merge.  (The apply
+backend used to known as the 'am' backend, but the name led to
+confusion as it looks like a verb instead of a noun.  Also, the merge
+backend used to be known as the interactive backend, but it is now
+used for non-interactive cases as well.  Both were renamed based on
+lower-level functionality that underpinned each.) There are some
+subtle differences in how these two backends behave:
 
 Empty commits
 ~~~~~~~~~~~~~
 
-The am backend unfortunately drops intentionally empty commits, i.e.
+The apply backend unfortunately drops intentionally empty commits, i.e.
 commits that started empty, though these are rare in practice.  It
 also drops commits that become empty and has no option for controlling
 this behavior.
 
-The interactive backend keeps intentionally empty commits.  Similar to
-the am backend, by default the interactive backend drops commits that
-become empty unless -i/--interactive is specified (in which case it
-stops and asks the user what to do).  The interactive backend also has
-an --empty={drop,keep,ask} option for changing the behavior of
-handling commits that become empty.
+The merge backend keeps intentionally empty commits.  Similar to the
+apply backend, by default the merge backend drops commits that become
+empty unless -i/--interactive is specified (in which case it stops and
+asks the user what to do).  The merge backend also has an
+--empty={drop,keep,ask} option for changing the behavior of handling
+commits that become empty.
 
 Directory rename detection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Due to the lack of accurate tree information (arising from
 constructing fake ancestors with the limited information available in
-patches), directory rename detection is disabled in the am backend.
+patches), directory rename detection is disabled in the apply backend.
 Disabled directory rename detection means that if one side of history
 renames a directory and the other adds new files to the old directory,
 then the new files will be left behind in the old directory without
 any warning at the time of rebasing that you may want to move these
 files into the new directory.
 
-Directory rename detection works with the merge and interactive
-backends to provide you warnings in such cases.
+Directory rename detection works with the merge backend to provide you
+warnings in such cases.
 
 Context
 ~~~~~~~
 
-The am backend works by creating a sequence of patches (by calling
+The apply backend works by creating a sequence of patches (by calling
 `format-patch` internally), and then applying the patches in sequence
 (calling `am` internally).  Patches are composed of multiple hunks,
 each with line numbers, a context region, and the actual changes.  The
@@ -654,7 +660,7 @@ Setting diff.context to a larger value may prevent such types of
 problems, but increases the chance of spurious conflicts (since it
 will require more lines of matching context to apply).
 
-The interactive backend works with a full copy of each relevant file,
+The merge backend works with a full copy of each relevant file,
 insulating it from these types of problems.
 
 Labelling of conflicts markers
@@ -662,34 +668,33 @@ Labelling of conflicts markers
 
 When there are content conflicts, the merge machinery tries to
 annotate each side's conflict markers with the commits where the
-content came from.  Since the am backend drops the original
+content came from.  Since the apply backend drops the original
 information about the rebased commits and their parents (and instead
 generates new fake commits based off limited information in the
 generated patches), those commits cannot be identified; instead it has
 to fall back to a commit summary.  Also, when merge.conflictStyle is
-set to diff3, the am backend will use "constructed merge base" to
+set to diff3, the apply backend will use "constructed merge base" to
 label the content from the merge base, and thus provide no information
 about the merge base commit whatsoever.
 
-The interactive backend works with the full commits on both sides of
-history and thus has no such limitations.
+The merge backend works with the full commits on both sides of history
+and thus has no such limitations.
 
 Hooks
 ~~~~~
 
-The am backend has not traditionally called the post-commit hook,
-while the merge/interactive backend has.  However, this was by
-accident of implementation rather than by design.  Both backends
-should have the same behavior, though it is not clear which one is
-correct.
+The apply backend has not traditionally called the post-commit hook,
+while the merge backend has.  However, this was by accident of
+implementation rather than by design.  Both backends should have the
+same behavior, though it is not clear which one is correct.
 
 Interruptability
 ~~~~~~~~~~~~~~~~
 
-The am backend has safety problems with an ill-timed interrupt; if the
-user presses Ctrl-C at the wrong time to try to abort the rebase, the
-rebase can enter a state where it cannot be aborted with a subsequent
-`git rebase --abort`.  The interactive backend does not appear to
+The apply backend has safety problems with an ill-timed interrupt; if
+the user presses Ctrl-C at the wrong time to try to abort the rebase,
+the rebase can enter a state where it cannot be aborted with a
+subsequent `git rebase --abort`.  The merge backend does not appear to
 suffer from the same shortcoming.  (See
 https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for
 details.)
@@ -707,9 +712,9 @@ completeness:
 
 * Progress, informational, and error messages: The two backends
   provide slightly different progress and informational messages.
-  Also, the am backend writes error messages (such as "Your files
-  would be overwritten...") to stdout, while the interactive backend
-  writes them to stderr.
+  Also, the apply backend writes error messages (such as "Your files
+  would be overwritten...") to stdout, while the merge backend writes
+  them to stderr.
 
 * State directories: The two backends keep their state in different
   directories under .git/
diff --git a/builtin/rebase.c b/builtin/rebase.c
index db8fd699d79..f3036f40c67 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -44,9 +44,8 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
 
 enum rebase_type {
 	REBASE_UNSPECIFIED = -1,
-	REBASE_AM,
+	REBASE_APPLY,
 	REBASE_MERGE,
-	REBASE_INTERACTIVE,
 	REBASE_PRESERVE_MERGES
 };
 
@@ -380,7 +379,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 	return ret;
 }
 
-static int run_rebase_interactive(struct rebase_options *opts,
+static int run_sequencer_rebase(struct rebase_options *opts,
 				  enum action command)
 {
 	unsigned flags = 0;
@@ -462,7 +461,7 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
 	 * If we ever want to remap --keep-empty to --empty=keep, insert:
 	 * 	opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
 	 */
-	opts->type = REBASE_INTERACTIVE;
+	opts->type = REBASE_MERGE;
 	return 0;
 }
 
@@ -555,28 +554,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 		warning(_("--[no-]rebase-cousins has no effect without "
 			  "--rebase-merges"));
 
-	return !!run_rebase_interactive(&opts, command);
+	return !!run_sequencer_rebase(&opts, command);
 }
 
-static int is_interactive(struct rebase_options *opts)
+static int is_merge(struct rebase_options *opts)
 {
-	return opts->type == REBASE_INTERACTIVE ||
+	return opts->type == REBASE_MERGE ||
 		opts->type == REBASE_PRESERVE_MERGES;
 }
 
-static void imply_interactive(struct rebase_options *opts, const char *option)
+static void imply_merge(struct rebase_options *opts, const char *option)
 {
 	switch (opts->type) {
-	case REBASE_AM:
+	case REBASE_APPLY:
 		die(_("%s requires an interactive rebase"), option);
 		break;
-	case REBASE_INTERACTIVE:
+	case REBASE_MERGE:
 	case REBASE_PRESERVE_MERGES:
 		break;
-	case REBASE_MERGE:
-		/* we now implement --merge via --interactive */
 	default:
-		opts->type = REBASE_INTERACTIVE; /* implied */
+		opts->type = REBASE_MERGE; /* implied */
 		break;
 	}
 }
@@ -785,7 +782,7 @@ static int finish_rebase(struct rebase_options *opts)
 	 * user should see them.
 	 */
 	run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
-	if (opts->type == REBASE_INTERACTIVE) {
+	if (opts->type == REBASE_MERGE) {
 		struct replay_opts replay = REPLAY_OPTS_INIT;
 
 		replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1118,8 +1115,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	int status;
 	const char *backend, *backend_func;
 
-	if (opts->type == REBASE_INTERACTIVE) {
-		/* Run builtin interactive rebase */
+	if (opts->type == REBASE_MERGE) {
+		/* Run sequencer-based rebase */
 		setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
 		if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
 			setenv("GIT_SEQUENCE_EDITOR", ":", 1);
@@ -1132,11 +1129,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 			opts->gpg_sign_opt = tmp;
 		}
 
-		status = run_rebase_interactive(opts, action);
+		status = run_sequencer_rebase(opts, action);
 		goto finished_rebase;
 	}
 
-	if (opts->type == REBASE_AM) {
+	if (opts->type == REBASE_APPLY) {
 		status = run_am(opts);
 		goto finished_rebase;
 	}
@@ -1190,7 +1187,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	add_var(&script_snippet, "git_format_patch_opt",
 		opts->git_format_patch_opt.buf);
 
-	if (is_interactive(opts) &&
+	if (is_merge(opts) &&
 	    !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
 		strbuf_addstr(&script_snippet,
 			      "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
@@ -1215,8 +1212,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 finished_rebase:
 	if (opts->dont_finish_rebase)
 		; /* do nothing */
-	else if (opts->type == REBASE_INTERACTIVE)
-		; /* interactive rebase cleans up after itself */
+	else if (opts->type == REBASE_MERGE)
+		; /* merge backend cleans up after itself */
 	else if (status == 0) {
 		if (!file_exists(state_dir_path("stopped-sha", opts)))
 			finish_rebase(opts);
@@ -1348,7 +1345,7 @@ static int parse_opt_am(const struct option *opt, const char *arg, int unset)
 	BUG_ON_OPT_NEG(unset);
 	BUG_ON_OPT_ARG(arg);
 
-	opts->type = REBASE_AM;
+	opts->type = REBASE_APPLY;
 
 	return 0;
 }
@@ -1361,7 +1358,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 	BUG_ON_OPT_NEG(unset);
 	BUG_ON_OPT_ARG(arg);
 
-	if (!is_interactive(opts))
+	if (!is_merge(opts))
 		opts->type = REBASE_MERGE;
 
 	return 0;
@@ -1376,7 +1373,7 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
 	BUG_ON_OPT_NEG(unset);
 	BUG_ON_OPT_ARG(arg);
 
-	opts->type = REBASE_INTERACTIVE;
+	opts->type = REBASE_MERGE;
 	opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
 
 	return 0;
@@ -1440,7 +1437,7 @@ static void set_reflog_action(struct rebase_options *options)
 	const char *env;
 	struct strbuf buf = STRBUF_INIT;
 
-	if (!is_interactive(options))
+	if (!is_merge(options))
 		return;
 
 	env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
@@ -1537,8 +1534,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "show-current-patch", &action,
 			    N_("show the patch file being applied or merged"),
 			    ACTION_SHOW_CURRENT_PATCH),
-		{ OPTION_CALLBACK, 0, "am", &options, NULL,
-			N_("use apply-mail strategies to rebase"),
+		{ OPTION_CALLBACK, 0, "apply", &options, NULL,
+			N_("use apply strategies to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
 			parse_opt_am },
 		{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
@@ -1615,7 +1612,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		die(_("It looks like 'git am' is in progress. Cannot rebase."));
 
 	if (is_directory(apply_dir())) {
-		options.type = REBASE_AM;
+		options.type = REBASE_APPLY;
 		options.state_dir = apply_dir();
 	} else if (is_directory(merge_dir())) {
 		strbuf_reset(&buf);
@@ -1627,7 +1624,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			strbuf_reset(&buf);
 			strbuf_addf(&buf, "%s/interactive", merge_dir());
 			if(file_exists(buf.buf)) {
-				options.type = REBASE_INTERACTIVE;
+				options.type = REBASE_MERGE;
 				options.flags |= REBASE_INTERACTIVE_EXPLICIT;
 			} else
 				options.type = REBASE_MERGE;
@@ -1667,12 +1664,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		die(_("No rebase in progress?"));
 	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
 
-	if (action == ACTION_EDIT_TODO && !is_interactive(&options))
+	if (action == ACTION_EDIT_TODO && !is_merge(&options))
 		die(_("The --edit-todo action can only be used during "
 		      "interactive rebase."));
 
 	if (trace2_is_enabled()) {
-		if (is_interactive(&options))
+		if (is_merge(&options))
 			trace2_cmd_mode("interactive");
 		else if (exec.nr)
 			trace2_cmd_mode("interactive-exec");
@@ -1748,7 +1745,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		goto cleanup;
 	}
 	case ACTION_QUIT: {
-		if (options.type == REBASE_INTERACTIVE) {
+		if (options.type == REBASE_MERGE) {
 			struct replay_opts replay = REPLAY_OPTS_INIT;
 
 			replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1831,7 +1828,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		argv_array_push(&options.git_am_opts, "-q");
 
 	if (options.empty != EMPTY_UNSPECIFIED)
-		imply_interactive(&options, "--empty");
+		imply_merge(&options, "--empty");
 
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
@@ -1841,7 +1838,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (exec.nr) {
 		int i;
 
-		imply_interactive(&options, "--exec");
+		imply_merge(&options, "--exec");
 
 		strbuf_reset(&buf);
 		for (i = 0; i < exec.nr; i++)
@@ -1857,7 +1854,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		else if (strcmp("no-rebase-cousins", rebase_merges))
 			die(_("Unknown mode: %s"), rebase_merges);
 		options.rebase_merges = 1;
-		imply_interactive(&options, "--rebase-merges");
+		imply_merge(&options, "--rebase-merges");
 	}
 
 	if (strategy_options.nr) {
@@ -1876,10 +1873,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.strategy) {
 		options.strategy = xstrdup(options.strategy);
 		switch (options.type) {
-		case REBASE_AM:
+		case REBASE_APPLY:
 			die(_("--strategy requires --merge or --interactive"));
 		case REBASE_MERGE:
-		case REBASE_INTERACTIVE:
 		case REBASE_PRESERVE_MERGES:
 			/* compatible */
 			break;
@@ -1892,34 +1888,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	if (options.type == REBASE_MERGE)
-		imply_interactive(&options, "--merge");
+		imply_merge(&options, "--merge");
 
 	if (options.root && !options.onto_name)
-		imply_interactive(&options, "--root without --onto");
+		imply_merge(&options, "--root without --onto");
 
 	if (isatty(2) && options.flags & REBASE_NO_QUIET)
 		strbuf_addstr(&options.git_format_patch_opt, " --progress");
 
-	if (options.git_am_opts.argc || options.type == REBASE_AM) {
-		/* all am options except -q are compatible only with --am */
+	if (options.git_am_opts.argc || options.type == REBASE_APPLY) {
+		/* all am options except -q are compatible only with --apply */
 		for (i = options.git_am_opts.argc - 1; i >= 0; i--)
 			if (strcmp(options.git_am_opts.argv[i], "-q"))
 				break;
 
 		if (i >= 0) {
-			if (is_interactive(&options))
-				die(_("cannot combine am options with either "
-				      "interactive or merge options"));
+			if (is_merge(&options))
+				die(_("cannot combine apply options with "
+				      "merge options"));
 			else
-				options.type = REBASE_AM;
+				options.type = REBASE_APPLY;
 		}
 	}
 
 	if (options.type == REBASE_UNSPECIFIED) {
 		if (!strcmp(options.default_backend, "merge"))
-			imply_interactive(&options, "--merge");
-		else if (!strcmp(options.default_backend, "am"))
-			options.type = REBASE_AM;
+			imply_merge(&options, "--merge");
+		else if (!strcmp(options.default_backend, "apply"))
+			options.type = REBASE_APPLY;
 		else
 			die(_("Unknown rebase backend: %s"),
 			    options.default_backend);
@@ -1927,11 +1923,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	switch (options.type) {
 	case REBASE_MERGE:
-	case REBASE_INTERACTIVE:
 	case REBASE_PRESERVE_MERGES:
 		options.state_dir = merge_dir();
 		break;
-	case REBASE_AM:
+	case REBASE_APPLY:
 		options.state_dir = apply_dir();
 		break;
 	default:
@@ -1946,7 +1941,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		else
 			options.empty = EMPTY_DROP;
 	}
-	if (reschedule_failed_exec > 0 && !is_interactive(&options))
+	if (reschedule_failed_exec > 0 && !is_merge(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
 	if (reschedule_failed_exec >= 0)
@@ -2247,7 +2242,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		diff_flush(&opts);
 	}
 
-	if (is_interactive(&options))
+	if (is_merge(&options))
 		goto run_rebase;
 
 	/* Detach HEAD and reset the tree */
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 0a491f23632..7c7e085043d 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea
 	test_cmp expect actual
 '
 
-test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' '
+test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--apply)' '
 	git checkout -B default-base master &&
 	git checkout -B default topic &&
 	git config branch.default.remote . &&
 	git config branch.default.merge refs/heads/default-base &&
-	git rebase --am &&
+	git rebase --apply &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual &&
 	git checkout default-base &&
 	git reset --hard HEAD^ &&
 	git checkout default &&
-	git rebase --am &&
+	git rebase --apply &&
 	git rev-parse --verify default-base >expect &&
 	git rev-parse default~1 >actual &&
 	test_cmp expect actual
@@ -224,9 +224,9 @@ test_expect_success 'cherry-picked commits and fork-point work together' '
 	test_cmp expect D
 '
 
-test_expect_success 'rebase --am -q is quiet' '
+test_expect_success 'rebase --apply -q is quiet' '
 	git checkout -b quiet topic &&
-	git rebase --am -q master >output.out 2>&1 &&
+	git rebase --apply -q master >output.out 2>&1 &&
 	test_must_be_empty output.out
 '
 
@@ -315,7 +315,7 @@ EOF
 	test_cmp From_.msg out
 '
 
-test_expect_success 'rebase --am and --show-current-patch' '
+test_expect_success 'rebase --apply and --show-current-patch' '
 	test_create_repo conflict-apply &&
 	(
 		cd conflict-apply &&
@@ -325,13 +325,13 @@ test_expect_success 'rebase --am and --show-current-patch' '
 		echo two >>init.t &&
 		git commit -a -m two &&
 		git tag two &&
-		test_must_fail git rebase --am -f --onto init HEAD^ &&
+		test_must_fail git rebase --apply -f --onto init HEAD^ &&
 		GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
 		grep "show.*$(git rev-parse two)" stderr
 	)
 '
 
-test_expect_success 'rebase --am and .gitattributes' '
+test_expect_success 'rebase --apply and .gitattributes' '
 	test_create_repo attributes &&
 	(
 		cd attributes &&
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index 50803958fd0..f18bae94507 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' '
 	)
 '
 
-test_expect_failure 'rebase --am: directory rename detected' '
+test_expect_failure 'rebase --apply: directory rename detected' '
 	(
 		cd dir-rename &&
 
 		git checkout B^0 &&
 
-		git -c merge.directoryRenames=true rebase --am A &&
+		git -c merge.directoryRenames=true rebase --apply A &&
 
 		git ls-files -s >out &&
 		test_line_count = 5 out &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index f964b2cd41d..2f51606e777 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int
 	git checkout conflict-branch &&
 	(
 		set_fake_editor &&
-		test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ &&
+		test_must_fail git rebase -f --apply --onto HEAD~2 HEAD~ &&
 		test_must_fail git rebase --edit-todo
 	) &&
 	git rebase --abort
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 7ce617fc1fb..61b76f33019 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' '
 '
 
 test_expect_success 'rebase against master twice' '
-	git rebase --am master >out &&
+	git rebase --apply master >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase against master twice with --force' '
-	git rebase --force-rebase --am master >out &&
+	git rebase --force-rebase --apply master >out &&
 	test_i18ngrep "Current branch topic is up to date, rebase forced" out
 '
 
 test_expect_success 'rebase against master twice from another branch' '
 	git checkout topic^ &&
-	git rebase --am master topic >out &&
+	git rebase --apply master topic >out &&
 	test_i18ngrep "Current branch topic is up to date" out
 '
 
 test_expect_success 'rebase fast-forward to master' '
 	git checkout topic^ &&
-	git rebase --am topic >out &&
+	git rebase --apply topic >out &&
 	test_i18ngrep "Fast-forwarded HEAD to topic" out
 '
 
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	git checkout -b reflog-topic start &&
 	test_commit reflog-to-rebase &&
 
-	git rebase --am reflog-onto &&
+	git rebase --apply reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' '
 	test_cmp expect actual &&
 
 	git checkout -b reflog-prefix reflog-to-rebase &&
-	GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto &&
+	GIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto &&
 	git log -g --format=%gs -3 >actual &&
 	cat >expect <<-\EOF &&
 	rebase finished: returning to refs/heads/reflog-prefix
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 3e318261703..97efea0f569 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -96,14 +96,14 @@ testrebase() {
 	'
 }
 
-testrebase " --am" .git/rebase-apply
+testrebase " --apply" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --am --quit' '
+test_expect_success 'rebase --apply --quit' '
 	cd "$work_dir" &&
 	# Clean up the state from the previous one
 	git reset --hard pre-rebase &&
-	test_must_fail git rebase --am master &&
+	test_must_fail git rebase --apply master &&
 	test_path_is_dir .git/rebase-apply &&
 	head_before=$(git rev-parse HEAD) &&
 	git rebase --quit &&
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index 3816159e207..b97ea623639 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -34,7 +34,7 @@ test_expect_success setup '
 	remove_progress_re="$(printf "s/.*\\r//")"
 '
 
-create_expected_success_am () {
+create_expected_success_apply () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
 	First, rewinding head to replay your work on top of it...
@@ -44,7 +44,7 @@ create_expected_success_am () {
 	EOF
 }
 
-create_expected_success_interactive () {
+create_expected_success_merge () {
 	q_to_cr >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
 	Applied autostash.
@@ -52,7 +52,7 @@ create_expected_success_interactive () {
 	EOF
 }
 
-create_expected_failure_am () {
+create_expected_failure_apply () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
 	First, rewinding head to replay your work on top of it...
@@ -64,7 +64,7 @@ create_expected_failure_am () {
 	EOF
 }
 
-create_expected_failure_interactive () {
+create_expected_failure_merge () {
 	cat >expected <<-EOF
 	$(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual)
 	Applying autostash resulted in conflicts.
@@ -101,9 +101,9 @@ testrebase () {
 
 	test_expect_success "rebase$type --autostash: check output" '
 		test_when_finished git branch -D rebased-feature-branch &&
-		suffix=${type#\ --} && suffix=${suffix:-am} &&
-		if test ${suffix} = "merge"; then
-			suffix=interactive
+		suffix=${type#\ --} && suffix=${suffix:-apply} &&
+		if test ${suffix} = "interactive"; then
+			suffix=merge
 		fi &&
 		create_expected_success_$suffix &&
 		sed "$remove_progress_re" <actual >actual2 &&
@@ -202,9 +202,9 @@ testrebase () {
 
 	test_expect_success "rebase$type: check output with conflicting stash" '
 		test_when_finished git branch -D rebased-feature-branch &&
-		suffix=${type#\ --} && suffix=${suffix:-am} &&
-		if test ${suffix} = "merge"; then
-			suffix=interactive
+		suffix=${type#\ --} && suffix=${suffix:-apply} &&
+		if test ${suffix} = "interactive"; then
+			suffix=merge
 		fi &&
 		create_expected_failure_$suffix &&
 		sed "$remove_progress_re" <actual >actual2 &&
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" '
 	git checkout feature-branch
 '
 
-testrebase " --am" .git/rebase-apply
+testrebase " --apply" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 testrebase " --interactive" .git/rebase-merge
 
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index e12304012a2..cf8dfd6c203 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -26,7 +26,7 @@ test_run_rebase () {
 		test_linear_range 'd e' c..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -50,7 +50,7 @@ test_run_rebase () {
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -66,7 +66,7 @@ test_run_rebase () {
 		test_linear_range 'd e' b..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -83,7 +83,7 @@ test_run_rebase () {
 		test_linear_range 'd e' branch-b..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -98,7 +98,7 @@ test_run_rebase () {
 		test_cmp_rev e HEAD
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
@@ -139,7 +139,7 @@ test_run_rebase () {
 		test_linear_range 'd i' h..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -154,7 +154,7 @@ test_run_rebase () {
 		test_linear_range 'd' h..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -169,7 +169,7 @@ test_run_rebase () {
 		test_linear_range 'd i' f..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -184,7 +184,7 @@ test_run_rebase () {
 		test_linear_range 'd gp i' h..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -212,7 +212,7 @@ test_run_rebase () {
 		test_linear_range 'j d k l' c..
 	"
 }
-test_run_rebase failure --am
+test_run_rebase failure --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -227,7 +227,7 @@ test_run_rebase () {
 		test_linear_range 'd k l' c..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -242,7 +242,7 @@ test_run_rebase () {
 		test_linear_range 'd k l' j..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -282,7 +282,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -297,7 +297,7 @@ test_run_rebase () {
 		test_linear_range 'x y' c..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -312,7 +312,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -328,7 +328,7 @@ test_run_rebase () {
 	"
 }
 
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -343,7 +343,7 @@ test_run_rebase () {
 		test_linear_range 'x y' m..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 test_have_prereq !REBASE_P || test_run_rebase failure -p
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
index cfb1ebc1ff9..98fc2a558a0 100755
--- a/t/t3424-rebase-empty.sh
+++ b/t/t3424-rebase-empty.sh
@@ -34,11 +34,11 @@ test_expect_success 'setup test repository' '
 	git commit -m "Five letters ought to be enough for anybody"
 '
 
-test_expect_failure 'rebase (am-backend)' '
+test_expect_failure 'rebase (apply-backend)' '
 	test_when_finished "git rebase --abort" &&
 	git checkout -B testing localmods &&
-	# rebase (--am) should not drop commits that start empty
-	git rebase upstream &&
+	# rebase (--apply) should not drop commits that start empty
+	git rebase --apply upstream &&
 
 	test_write_lines D C B A >expect &&
 	git log --format=%s >actual &&
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
index 19700b025b5..e42faa44e74 100755
--- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
@@ -54,7 +54,7 @@ test_run_rebase () {
 		test_linear_range 'n o' e..
 	"
 }
-test_run_rebase success --am
+test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
 
@@ -70,7 +70,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" d..
 	"
 }
-test_run_rebase success 'n o e' --am
+test_run_rebase success 'n o e' --apply
 test_run_rebase success 'n o e' -m
 test_run_rebase success 'n o e' -i
 
@@ -86,7 +86,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' --am
+test_run_rebase success 'd n o e' --apply
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
@@ -102,7 +102,7 @@ test_run_rebase () {
 		test_linear_range "\'"$expected"\'" c..
 	"
 }
-test_run_rebase success 'd n o e' --am
+test_run_rebase success 'd n o e' --apply
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 4b3cecce560..6c9d4a13758 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,8 +28,8 @@ test_rebase_same_head () {
 	shift &&
 	cmp_f="$1" &&
 	shift &&
-	test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" &&
-	test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*"
+	test_rebase_same_head_ $status_n $what_n $cmp_n " --apply" "$*" &&
+	test_rebase_same_head_ $status_f $what_f $cmp_f " --apply --no-ff" "$*"
 	test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
 	test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index a8a73616e46..80750a817e9 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
-test_expect_success 'git rebase --am' '
+test_expect_success 'git rebase --apply' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --am --onto A B &&
+	test_must_fail git rebase --apply --onto A B &&
 	echo C > foo &&
 	git add foo &&
 	git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase --am' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --am --skip' '
+test_expect_success 'git rebase --apply --skip' '
 	git reset --hard D &&
 	clear_hook_input &&
-	test_must_fail git rebase --am --onto A B &&
+	test_must_fail git rebase --apply --onto A B &&
 	test_must_fail git rebase --skip &&
 	echo D > foo &&
 	git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --am --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --am --skip the last one' '
+test_expect_success 'git rebase --apply --skip the last one' '
 	git reset --hard F &&
 	clear_hook_input &&
-	test_must_fail git rebase --am --onto D A &&
+	test_must_fail git rebase --apply --onto D A &&
 	git rebase --skip &&
 	echo rebase >expected.args &&
 	cat >expected.data <<-EOF &&
@@ -128,7 +128,7 @@ test_expect_success 'git rebase -m --skip' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase with implicit use of interactive backend' '
+test_expect_success 'git rebase with implicit use of merge backend' '
 	git reset --hard D &&
 	clear_hook_input &&
 	test_must_fail git rebase --keep-empty --onto A B &&
@@ -143,7 +143,7 @@ test_expect_success 'git rebase with implicit use of interactive backend' '
 	verify_hook_input
 '
 
-test_expect_success 'git rebase --skip with implicit use of interactive backend' '
+test_expect_success 'git rebase --skip with implicit use of merge backend' '
 	git reset --hard D &&
 	clear_hook_input &&
 	test_must_fail git rebase --keep-empty --onto A B &&
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 4f9e7f7ff6b..2f86fca0428 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -297,7 +297,7 @@ test_expect_success '--rebase (merge) fast forward' '
 test_expect_success '--rebase (am) fast forward' '
 	git reset --hard before-rebase &&
 
-	git -c rebase.backend=am pull --rebase . ff &&
+	git -c rebase.backend=apply pull --rebase . ff &&
 	test_cmp_rev HEAD ff &&
 
 	# The above only validates the result.  Did we actually bypass rebase?
diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh
index d383ce81302..f4655bb358f 100755
--- a/t/t6047-diff3-conflict-markers.sh
+++ b/t/t6047-diff3-conflict-markers.sh
@@ -199,11 +199,11 @@ test_expect_success 'rebase --merge describes parent of commit being picked' '
 	)
 '
 
-test_expect_success 'rebase --am describes fake ancestor base' '
+test_expect_success 'rebase --apply describes fake ancestor base' '
 	(
 		cd rebase &&
 		git rebase --abort &&
-		test_must_fail git -c merge.conflictstyle=diff3 rebase --am master &&
+		test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master &&
 		grep "||||||| constructed merge base" file
 	)
 '
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index d22b0acf2ac..29518e0949b 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' '
 '
 
 
-test_expect_success 'status when rebase --am in progress before resolving conflicts' '
+test_expect_success 'status when rebase --apply in progress before resolving conflicts' '
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF
 '
 
 
-test_expect_success 'status when rebase --am in progress before rebase --continue' '
+test_expect_success 'status when rebase --apply in progress before rebase --continue' '
 	git reset --hard rebase_conflicts &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	echo three >main.txt &&
 	git add main.txt &&
 	cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF
 '
 
 
-test_expect_success 'status when rebase --am conflicts with statushints disabled' '
+test_expect_success 'status when rebase --apply conflicts with statushints disabled' '
 	git reset --hard master &&
 	git checkout -b statushints_disabled &&
 	test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase --am conflicts with statushints disabled
 	test_commit three_statushints main.txt three &&
 	test_when_finished "git rebase --abort" &&
 	ONTO=$(git rev-parse --short HEAD^^) &&
-	test_must_fail git rebase --am HEAD^ --onto HEAD^^ &&
+	test_must_fail git rebase --apply HEAD^ --onto HEAD^^ &&
 	cat >expected <<EOF &&
 rebase in progress; onto $ONTO
 You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 7ca35d358d6..ab5da2cabc4 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -193,7 +193,7 @@ test_expect_success 'prompt - rebase am' '
 	printf " (b2|REBASE 1/3)" >expected &&
 	git checkout b2 &&
 	test_when_finished "git checkout master" &&
-	test_must_fail git rebase --am b1 b2 &&
+	test_must_fail git rebase --apply b1 b2 &&
 	test_when_finished "git rebase --abort" &&
 	__git_ps1 >"$actual" &&
 	test_cmp expected "$actual"
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
  2020-02-13 18:54           ` Elijah Newren
@ 2020-02-16 14:46             ` Phillip Wood
  0 siblings, 0 replies; 161+ messages in thread
From: Phillip Wood @ 2020-02-16 14:46 UTC (permalink / raw)
  To: Elijah Newren, Phillip Wood
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Denton Liu, Junio C Hamano, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder, Emily Shaffer

Hi Elijah

On 13/02/2020 18:54, Elijah Newren wrote:
> On Mon, Feb 10, 2020 at 6:27 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> Hi Elijah
>>
>> On 16/01/2020 06:14, Elijah Newren via GitGitGadget wrote:
>>> From: Elijah Newren <newren@gmail.com>
>>>
>>> As established in the previous commit and commit b00bf1c9a8dd
>>> (git-rebase: make --allow-empty-message the default, 2018-06-27), the
>>> behavior for rebase with different backends in various edge or corner
>>> cases is often more happenstance than design.  This commit addresses
>>> another such corner case: commits which "become empty".
>>>
>>> A careful reader may note that there are two types of commits which would
>>> become empty due to a rebase:
>>>
>>>     * [clean cherry-pick] Commits which are clean cherry-picks of upstream
>>>       commits, as determined by `git log --cherry-mark ...`.  Re-applying
>>>       these commits would result in an empty set of changes and a
>>>       duplicative commit message; i.e. these are commits that have
>>>       "already been applied" upstream.
>>>
>>>     * [become empty] Commits which are not empty to start, are not clean
>>>       cherry-picks of upstream commits, but which still become empty after
>>>       being rebased.  This happens e.g. when a commit has changes which
>>>       are a strict subset of the changes in an upstream commit, or when
>>>       the changes of a commit can be found spread across or among several
>>>       upstream commits.
>>>
>>> Clearly, in both cases the changes in the commit in question are found
>>> upstream already, but the commit message may not be in the latter case.
>>>
>>> When cherry-mark can determine a commit is already upstream, then
>>> because of how cherry-mark works this means the upstream commit message
>>> was about the *exact* same set of changes.  Thus, the commit messages
>>> can be assumed to be fully interchangeable (and are in fact likely to be
>>> completely identical).  As such, the clean cherry-pick case represents a
>>> case when there is no information to be gained by keeping the extra
>>> commit around.  All rebase types have always dropped these commits, and
>>> no one to my knowledge has ever requested that we do otherwise.
>>>
>>> For many of the become empty cases (and likely even most), we will also
>>> be able to drop the commit without loss of information -- but this isn't
>>> quite always the case.  Since these commits represent cases that were
>>> not clean cherry-picks, there is no upstream commit message explaining
>>> the same set of changes.  Projects with good commit message hygiene will
>>> likely have the explanation from our commit message contained within or
>>> spread among the relevant upstream commits, but not all projects run
>>> that way.  As such, the commit message of the commit being rebased may
>>> have reasoning that suggests additional changes that should be made to
>>> adapt to the new base, or it may have information that someone wants to
>>> add as a note to another commit, or perhaps someone even wants to create
>>> an empty commit with the commit message as-is.
>>>
>>> Junio commented on the "become-empty" types of commits as follows[1]:
>>>
>>>       WRT a change that ends up being empty (as opposed to a change that
>>>       is empty from the beginning), I'd think that the current behaviour
>>>       is desireable one.  "am" based rebase is solely to transplant an
>>>       existing history and want to stop much less than "interactive" one
>>>       whose purpose is to polish a series before making it publishable,
>>>       and asking for confirmation ("this has become empty--do you want to
>>>       drop it?") is more appropriate from the workflow point of view.
>>>
>>> [1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
>>>
>>> I would simply add that his arguments for "am"-based rebases actually
>>> apply to all non-explicitly-interactive rebases.  Also, since we are
>>> stating that different cases should have different defaults, it may be
>>> worth providing a flag to allow users to select which behavior they want
>>> for these commits.
>>>
>>> Introduce a new command line flag for selecting the desired behavior:
>>>       --empty={drop,keep,ask}
>>> with the definitions:
>>>       drop: drop commits which become empty
>>>       keep: keep commits which become empty
>>>       ask:  provide the user a chance to interact and pick what to do with
>>>             commits which become empty on a case-by-case basis
>>>
>>> In line with Junio's suggestion, if the --empty flag is not specified,
>>> pick defaults as follows:
>>>       explicitly interactive: ask
>>>       otherwise: drop
>>
>> Looking at the implementation there is a third option - if `--exec` is
>> given without `-i` then the default is "keep". I'm not sure if having
>> different defaults is convenient or confusing but don't feel that
>> strongly about it.
> 
> Heh, in https://lore.kernel.org/git/404424d7-f520-8f89-efef-ca03e66fcd43@gmail.com/
> you argued that having different defaults was confusing and sounded
> like you felt strongly about it.  Granted, there has been a lot of
> simplification to the implementation (and description) since then but
> I'm still inclined to go with the simpler and more easily explained
> behavior for the defaults based on what you said there.

I would still prefer a common default but I can see the logic to the 
defaults you are proposing I just think it makes it hard to explain and 
it'll end up surprising someone who was expecting a different default. 
When I said I didn't feel that strongly I meant that it wasn't worth 
holding up this just for this.

Best Wishes

Phillip

>> I've got a few minor comments below (the mains ones
>> are saying which commit has been dropped and testing the default
>> behavior when --empty is not given) but basically I like the new patch.
>> Thanks for working on this, the commit message does a good job of
>> explaining the changes.
> 
> :-)
> 
>>> Signed-off-by: Elijah Newren <newren@gmail.com>
>>> ---
>>>    Documentation/git-rebase.txt | 27 ++++++++++++++++---
>>>    builtin/rebase.c             | 52 ++++++++++++++++++++++++++++++++++++
>>>    sequencer.c                  | 48 +++++++++++++++++++++++++--------
>>>    sequencer.h                  |  1 +
>>>    t/t3424-rebase-empty.sh      | 50 +++++++++++++++++++++++++++++-----
>>>    t/t3427-rebase-subtree.sh    |  8 +++---
>>>    6 files changed, 161 insertions(+), 25 deletions(-)
>>>
>>> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
>>> index 1d19542d79..551a91d764 100644
>>> --- a/Documentation/git-rebase.txt
>>> +++ b/Documentation/git-rebase.txt
>>> @@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below.
>>>        original branch. The index and working tree are also left
>>>        unchanged as a result.
>>>
>>> +--empty={drop,keep,ask}::
>>> +     How to handle commits that are not empty to start and are not
>>> +     clean cherry-picks of any upstream commit, but which become
>>> +     empty after rebasing (because they contain a subset of already
>>> +     upstream changes).  With drop (the default), commits that
>>> +     become empty are dropped.  With keep, such commits are kept.
>>> +     With ask (implied by --interactive), the rebase will halt when
>>> +     an empty commit is applied allowing you to choose whether to
>>> +     drop it, edit files more, or just commit the empty changes.
>>
>> We should probably document the default for --exec without -i
> 
> I did, but I guess it wasn't clear enough.  Maybe I should add "Note
> that other options like --exec will use the default of drop unless
> -i/--interactive is specified."?
> 
>>> +Note that commits which start empty are kept, and commits which are
>>> +clean cherry-picks (as determined by `git log --cherry-mark ...`) are
>>> +always dropped.
>>> ++
>>> +See also INCOMPATIBLE OPTIONS below.
>>> +
>>>    --keep-empty::
>>>        No-op.  Rebasing commits that started empty (had no change
>>>        relative to their parent) used to fail and this option would
>>> @@ -561,6 +577,7 @@ are incompatible with the following options:
>>>     * --interactive
>>>     * --exec
>>>     * --keep-empty
>>> + * --empty=
>>>     * --edit-todo
>>>     * --root when used in combination with --onto
>>>
>>> @@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible:
>>>     * --preserve-merges and --interactive
>>>     * --preserve-merges and --signoff
>>>     * --preserve-merges and --rebase-merges
>>> + * --preserve-merges and --empty=
>>>     * --keep-base and --onto
>>>     * --keep-base and --root
>>>
>>> @@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice.  It
>>>    also drops commits that become empty and has no option for controlling
>>>    this behavior.
>>>
>>> -The interactive backend keeps intentionally empty commits.
>>> -Unfortunately, it always halts whenever it runs across a commit that
>>> -becomes empty, even when the rebase is not explicitly interactive.
>>> +The interactive backend keeps intentionally empty commits.  Similar to
>>> +the am backend, by default the interactive backend drops commits that
>>> +become empty unless -i/--interactive is specified (in which case it
>>> +stops and asks the user what to do).  The interactive backend also has
>>> +an --empty={drop,keep,ask} option for changing the behavior of
>>> +handling commits that become empty.
>>>
>>>    Directory rename detection
>>>    ~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> diff --git a/builtin/rebase.c b/builtin/rebase.c
>>> index 537b3241ce..c299869e7b 100644
>>> --- a/builtin/rebase.c
>>> +++ b/builtin/rebase.c
>>> @@ -50,8 +50,16 @@ enum rebase_type {
>>>        REBASE_PRESERVE_MERGES
>>>    };
>>>
>>> +enum empty_type {
>>> +     EMPTY_UNSPECIFIED = -1,
>>> +     EMPTY_DROP,
>>> +     EMPTY_KEEP,
>>> +     EMPTY_ASK
>>> +};
>>> +
>>>    struct rebase_options {
>>>        enum rebase_type type;
>>> +     enum empty_type empty;
>>>        const char *state_dir;
>>>        struct commit *upstream;
>>>        const char *upstream_name;
>>> @@ -91,6 +99,7 @@ struct rebase_options {
>>>
>>>    #define REBASE_OPTIONS_INIT {                               \
>>>                .type = REBASE_UNSPECIFIED,             \
>>> +             .empty = EMPTY_UNSPECIFIED,             \
>>>                .flags = REBASE_NO_QUIET,               \
>>>                .git_am_opts = ARGV_ARRAY_INIT,         \
>>>                .git_format_patch_opt = STRBUF_INIT     \
>>> @@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>>>                replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
>>>        replay.allow_empty = 1;
>>>        replay.allow_empty_message = opts->allow_empty_message;
>>> +     replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
>>> +     replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
>>>        replay.verbose = opts->flags & REBASE_VERBOSE;
>>>        replay.reschedule_failed_exec = opts->reschedule_failed_exec;
>>>        replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
>>> @@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
>>>
>>>        BUG_ON_OPT_ARG(arg);
>>>
>>> +     /*
>>> +      * If we ever want to remap --keep-empty to --empty=keep, insert:
>>> +      *      opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
>>> +      */
>>>        opts->type = REBASE_INTERACTIVE;
>>>        return 0;
>>>    }
>>> @@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
>>>        return 0;
>>>    }
>>>
>>> +static enum empty_type parse_empty_value(const char *value)
>>> +{
>>> +     if (!strcasecmp(value, "drop"))
>>> +             return EMPTY_DROP;
>>> +     else if (!strcasecmp(value, "keep"))
>>> +             return EMPTY_KEEP;
>>> +     else if (!strcasecmp(value, "ask"))
>>> +             return EMPTY_ASK;
>>> +
>>> +     die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
>>> +}
>>> +
>>> +static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
>>> +{
>>> +     struct rebase_options *options = opt->value;
>>> +     enum empty_type value = parse_empty_value(arg);
>>> +
>>> +     BUG_ON_OPT_NEG(unset);
>>> +
>>> +     options->empty = value;
>>> +     return 0;
>>> +}
>>> +
>>>    static void NORETURN error_on_missing_default_upstream(void)
>>>    {
>>>        struct branch *current_branch = branch_get(NULL);
>>> @@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>>>                                 "ignoring them"),
>>>                              REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>>>                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
>>> +             OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
>>> +                            N_("how to handle empty commits"),
>>
>> Maybe we should say "how to handle commits that become empty" to
>> distinguish them from commits that start empty which we always keep
> 
> Ooh, good catch; will fix.
> 
>>> +                            PARSE_OPT_NONEG, parse_opt_empty),
>>>                { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>>>                        N_("(DEPRECATED) keep empty commits"),
>>>                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
>>> @@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>>>        if (!(options.flags & REBASE_NO_QUIET))
>>>                argv_array_push(&options.git_am_opts, "-q");
>>>
>>> +     if (options.empty != EMPTY_UNSPECIFIED)
>>> +             imply_interactive(&options, "--empty");
>>> +
>>>        if (gpg_sign) {
>>>                free(options.gpg_sign_opt);
>>>                options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
>>> @@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>>>                break;
>>>        }
>>>
>>> +     if (options.empty == EMPTY_UNSPECIFIED) {
>>> +             if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
>>> +                     options.empty = EMPTY_ASK;
>>> +             else if (exec.nr > 0)
>>> +                     options.empty = EMPTY_KEEP;
>>> +             else
>>> +                     options.empty = EMPTY_DROP;
>>> +     }
>>>        if (reschedule_failed_exec > 0 && !is_interactive(&options))
>>>                die(_("--reschedule-failed-exec requires "
>>>                      "--exec or --interactive"));
>>> diff --git a/sequencer.c b/sequencer.c
>>> index c21fc202b1..354d0b5a38 100644
>>> --- a/sequencer.c
>>> +++ b/sequencer.c
>>> @@ -158,6 +158,8 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
>>>    static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
>>>    static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
>>>    static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
>>> +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
>>> +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
>>>
>>>    static int git_sequencer_config(const char *k, const char *v, void *cb)
>>>    {
>>> @@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit)
>>>    }
>>>
>>>    /*
>>> - * Do we run "git commit" with "--allow-empty"?
>>> + * Should empty commits be allowed?  Return status:
>>> + *    <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
>>> + *     0: Halt on empty commit
>>> + *     1: Allow empty commit
>>> + *     2: Drop empty commit
>>>     */
>>>    static int allow_empty(struct repository *r,
>>>                       struct replay_opts *opts,
>>> @@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,
>>>        int index_unchanged, originally_empty;
>>>
>>>        /*
>>> -      * Three cases:
>>> +      * Four cases:
>>>         *
>>>         * (1) we do not allow empty at all and error out.
>>>         *
>>> -      * (2) we allow ones that were initially empty, but
>>> -      * forbid the ones that become empty;
>>> +      * (2) we allow ones that were initially empty, and
>>> +      *     just drop the ones that become empty
>>>         *
>>> -      * (3) we allow both.
>>> +      * (3) we allow ones that were initially empty, but
>>> +      *     halt for the ones that become empty;
>>> +      *
>>> +      * (4) we allow both.
>>>         */
>>>        if (!opts->allow_empty)
>>>                return 0; /* let "git commit" barf as necessary */
>>> @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r,
>>>        originally_empty = is_original_commit_empty(commit);
>>>        if (originally_empty < 0)
>>>                return originally_empty;
>>> -     if (!originally_empty)
>>> -             return 0;
>>> -     else
>>> +     if (originally_empty)
>>>                return 1;
>>> +     else if (opts->drop_redundant_commits)
>>> +             return 2;
>>> +     else
>>> +             return 0;
>>>    }
>>>
>>>    static struct {
>>> @@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,
>>>        char *author = NULL;
>>>        struct commit_message msg = { NULL, NULL, NULL, NULL };
>>>        struct strbuf msgbuf = STRBUF_INIT;
>>> -     int res, unborn = 0, reword = 0, allow;
>>> +     int res, unborn = 0, reword = 0, allow, drop_commit;
>>>
>>>        if (opts->no_commit) {
>>>                /*
>>> @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r,
>>>                goto leave;
>>>        }
>>>
>>> +     drop_commit = 0;
>>>        allow = allow_empty(r, opts, commit);
>>>        if (allow < 0) {
>>>                res = allow;
>>>                goto leave;
>>> -     } else if (allow)
>>> +     } else if (allow == 1) {
>>>                flags |= ALLOW_EMPTY;
>>> -     if (!opts->no_commit) {
>>> +     } else if (allow == 2) {
>>> +             drop_commit = 1;
>>> +             fprintf(stderr, _("No changes -- Patch already applied.\n"));
>>
>> nit pick - usually messages start with a lowercase letter. Would it be
>> helpful to explicitly state which commit is being dropped as well as
>> why? Something like
>>     dropping <oid> <subject> - patch contents already upstream
> 
> I was actually just trying to mimic the am-backend here, and copied
> its message verbatim for this case (see am_run() in builtin/am.c).
> However, your version does seem more helpful and informative.  I'll
> look into it implementing it here.
> 
>>
>>> +     } // else allow == 0 and there's nothing special to do
>>
>> We don't use // for comments
> 
> Oops, sorry.  Will fix.
> 
>>
>>> +     if (!opts->no_commit && !drop_commit) {
>>>                if (author || command == TODO_REVERT || (flags & AMEND_MSG))
>>>                        res = do_commit(r, msg_file, author, opts, flags);
>>>                else
>>> @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)
>>>                if (file_exists(rebase_path_reschedule_failed_exec()))
>>>                        opts->reschedule_failed_exec = 1;
>>>
>>> +             if (file_exists(rebase_path_drop_redundant_commits()))
>>> +                     opts->drop_redundant_commits = 1;
>>> +
>>> +             if (file_exists(rebase_path_keep_redundant_commits()))
>>> +                     opts->keep_redundant_commits = 1;
>>> +
>>>                read_strategy_opts(opts, &buf);
>>>                strbuf_release(&buf);
>>>
>>> @@ -2574,6 +2596,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
>>>                write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
>>>        if (opts->signoff)
>>>                write_file(rebase_path_signoff(), "--signoff\n");
>>> +     if (opts->drop_redundant_commits)
>>> +             write_file(rebase_path_drop_redundant_commits(), "%s", "");
>>> +     if (opts->keep_redundant_commits)
>>> +             write_file(rebase_path_keep_redundant_commits(), "%s", "");
>>>        if (opts->reschedule_failed_exec)
>>>                write_file(rebase_path_reschedule_failed_exec(), "%s", "");
>>>
>>> diff --git a/sequencer.h b/sequencer.h
>>> index c165e0ff25..3b0ab9141f 100644
>>> --- a/sequencer.h
>>> +++ b/sequencer.h
>>> @@ -39,6 +39,7 @@ struct replay_opts {
>>>        int allow_rerere_auto;
>>>        int allow_empty;
>>>        int allow_empty_message;
>>> +     int drop_redundant_commits;
>>>        int keep_redundant_commits;
>>>        int verbose;
>>>        int quiet;
>>> diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
>>> index 22d97e143b..dcb4cb4751 100755
>>> --- a/t/t3424-rebase-empty.sh
>>> +++ b/t/t3424-rebase-empty.sh
>>> @@ -34,7 +34,7 @@ test_expect_success 'setup test repository' '
>>>        git commit -m "Five letters ought to be enough for anybody"
>>>    '
>>>
>>> -test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
>>> +test_expect_failure 'rebase (am-backend)' '
>>>        test_when_finished "git rebase --abort" &&
>>>        git checkout -B testing localmods &&
>>>        # rebase (--am) should not drop commits that start empty
>>> @@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
>>>        test_cmp expect actual
>>>    '
>>>
>>> -test_expect_failure 'rebase --merge with a variety of empty commits' '
>>> -     test_when_finished "git rebase --abort" &&
>>> +test_expect_success 'rebase --merge --empty=drop' '
>>>        git checkout -B testing localmods &&
>>> -     # rebase --merge should not halt on the commit that becomes empty
>>> -     git rebase --merge upstream &&
>>> +     git rebase --merge --empty=drop upstream &&
>>> +
>>> +     test_write_lines D C B A >expect &&
>>> +     git log --format=%s >actual &&
>>> +     test_cmp expect actual
>>> +'
>>> +
>>> +test_expect_success 'rebase --merge --empty=keep' '
>>> +     git checkout -B testing localmods &&
>>> +     git rebase --merge --empty=keep upstream &&
>>> +
>>> +     test_write_lines D C2 C B A >expect &&
>>> +     git log --format=%s >actual &&
>>> +     test_cmp expect actual
>>> +'
>>> +
>>> +test_expect_success 'rebase --merge --empty=ask' '
>>> +     git checkout -B testing localmods &&
>>> +     test_must_fail git rebase --merge --empty=ask upstream &&
>>> +
>>> +     git rebase --skip &&
>>>
>>>        test_write_lines D C B A >expect &&
>>>        git log --format=%s >actual &&
>>> @@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' '
>>>
>>>    GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>>>
>>> -test_expect_success 'rebase --interactive with a variety of empty commits' '
>>> +test_expect_success 'rebase --interactive --empty=drop' '
>>> +     git checkout -B testing localmods &&
>>> +     git rebase --interactive --empty=drop upstream &&
>>> +
>>> +     test_write_lines D C B A >expect &&
>>> +     git log --format=%s >actual &&
>>> +     test_cmp expect actual
>>> +'
>>> +
>>> +test_expect_success 'rebase --interactive --empty=keep' '
>>> +     git checkout -B testing localmods &&
>>> +     git rebase --interactive --empty=keep upstream &&
>>> +
>>> +     test_write_lines D C2 C B A >expect &&
>>> +     git log --format=%s >actual &&
>>> +     test_cmp expect actual
>>> +'
>>> +
>>> +test_expect_success 'rebase --interactive --empty=ask' '
>>>        git checkout -B testing localmods &&
>>> -     test_must_fail git rebase --interactive upstream &&
>>> +     test_must_fail git rebase --interactive --empty=ask upstream &&
>>>
>>>        git rebase --skip &&
>>
>> As the default if --empty is not given is supposed to vary depending on
>> the other options given it would be good to test that I think
> 
> I'll add some tests.
> 
> 
> Thanks for the thorough review!
> Elijah
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 00/20] rebase: make the default backend configurable and change the default
  2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
                           ` (19 preceding siblings ...)
  2020-02-15 21:36         ` [PATCH v5 20/20] rebase: rename the two primary rebase backends Elijah Newren via GitGitGadget
@ 2020-02-16 15:01         ` Phillip Wood
  20 siblings, 0 replies; 161+ messages in thread
From: Phillip Wood @ 2020-02-16 15:01 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget, git
  Cc: Johannes.Schindelin, phillip.wood, liu.denton, gitster, plroskin,
	alban.gruin, szeder.dev, jrnieder, emilyshaffer, Elijah Newren

Hi Elijah

On 15/02/2020 21:36, Elijah Newren via GitGitGadget wrote:
> This is v5 of en/rebase-backend based on v2.25.0. This series does a lot of
> work around making the default rebase backend configurable, and switches the
> default from the apply backend to the merge one.
> 
> Changes since v4:
> 
>   * Added a note about an unfortunately-timed Ctrl-C to the docs, as
>     suggested by SZEDER.

That's a good idea - I don't know why builtin/rebase.c only calls 
rebase_write_basic_state() which writes head-name after it has run 
format-patch and am. We write the patches to the state directory so it's 
not like it saves us from having to clean up.

>   * Addressed feedback from Phillip
>   * Added a new patch at the end renaming the two backends. PLEASE take a
>     look at its commit message for rationale and let me know if you agree
>     with it.

I had a look and I think it is a good idea - merge and apply make much 
more sense that interactive and am

> Note that in particular, the last patch at the end could have been partially
> squashed into other patches, but if people objected to the patch in general
> I wanted Junio to be able to take the rest of the series and just drop that
> patch.
> 
> As with v4, there are some things mentioned earlier that are not addressed:
> 
>   * I did not remove the --am (now --apply) option as suggested by Phillip,
>     since Junio and Phillip were still discussing whether it is
>     wanted/needed. I kind of like having it, but have no strong opinion.
>   * I noted the post-commit hook in the differences between backends. Emily
>     is investigating what changes need to happen there, so I merely
>     documented the existing differences.
>   * I did not address the last two items Jonathan brought up as I couldn't
>     find enough information to reproduce or understand the problems.
> 
> Elijah Newren (20):
>    git-rebase.txt: update description of --allow-empty-message
>    t3404: directly test the behavior of interest
>    rebase (interactive-backend): make --keep-empty the default
>    rebase (interactive-backend): fix handling of commits that become
>      empty
>    t3406: simplify an already simple test
>    rebase, sequencer: remove the broken GIT_QUIET handling
>    rebase: make sure to pass along the quiet flag to the sequencer
>    rebase: fix handling of restrict_revision
>    t3432: make these tests work with either am or merge backends
>    rebase: allow more types of rebases to fast-forward
>    git-rebase.txt: add more details about behavioral differences of
>      backends
>    rebase: move incompatibility checks between backend options a bit
>      earlier
>    rebase: add an --am option
>    git-prompt: change the prompt for interactive-based rebases
>    rebase: drop '-i' from the reflog for interactive-based rebases
>    rebase tests: mark tests specific to the am-backend with --am
>    rebase tests: repeat some tests using the merge backend instead of am
>    rebase: make the backend configurable via config setting
>    rebase: change the default backend from "am" to "merge"
>    rebase: rename the two primary rebase backends
> 
>   Documentation/config/rebase.txt        |   6 +
>   Documentation/git-rebase.txt           | 170 +++++++++++++---
>   builtin/rebase.c                       | 257 +++++++++++++++++--------
>   contrib/completion/git-prompt.sh       |   6 +-
>   rebase-interactive.c                   |   7 +-
>   rebase-interactive.h                   |   2 +-
>   sequencer.c                            |  82 +++++---
>   sequencer.h                            |   3 +-
>   t/t3400-rebase.sh                      |  40 +++-
>   t/t3401-rebase-and-am-rename.sh        |   4 +-
>   t/t3404-rebase-interactive.sh          |  19 +-
>   t/t3406-rebase-message.sh              |  19 +-
>   t/t3407-rebase-abort.sh                |   6 +-
>   t/t3420-rebase-autostash.sh            |  22 +--
>   t/t3421-rebase-topology-linear.sh      |  48 ++---
>   t/t3424-rebase-empty.sh                | 126 ++++++++++++
>   t/t3425-rebase-topology-merges.sh      |   8 +-
>   t/t3427-rebase-subtree.sh              |  12 +-
>   t/t3432-rebase-fast-forward.sh         |  54 +++---
>   t/t5407-post-rewrite-hook.sh           |  16 +-
>   t/t5520-pull.sh                        |  27 ++-
>   t/t6047-diff3-conflict-markers.sh      |  13 +-
>   t/t7512-status-help.sh                 |  12 +-
>   t/t9106-git-svn-commit-diff-clobber.sh |   3 +-
>   t/t9903-bash-prompt.sh                 |   8 +-
>   25 files changed, 693 insertions(+), 277 deletions(-)
>   create mode 100755 t/t3424-rebase-empty.sh
> 
> 
> base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5
> Pull-Request: https://github.com/git/git/pull/679
> 
> Range-diff vs v4:
> 
>    1:  3ea48d53940 =  1:  3ea48d53940 git-rebase.txt: update description of --allow-empty-message
>    2:  10fdd162a05 =  2:  10fdd162a05 t3404: directly test the behavior of interest
>    3:  179f82ab83e !  3:  74cab9a32b3 rebase (interactive-backend): make --keep-empty the default
>       @@ -4,13 +4,13 @@
>        
>            Different rebase backends have different treatment for commits which
>            start empty (i.e. have no changes relative to their parent), and the
>       -    --keep-empty option was added at some point to allow adjusting behavior
>       -    for the interactive backend.  The handling of commits which start empty
>       -    is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
>       -    --allow-empty-message the default, 2018-06-27), which pointed out that
>       -    the behavior for various backends is often more happenstance than
>       -    design.  The specific change made in that commit is actually quite
>       -    relevant as well and much of the logic there directly applies here.
>       +    --keep-empty option was added at some point to allow adjusting behavior.
>       +    The handling of commits which start empty is actually quite similar to
>       +    commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
>       +    2018-06-27), which pointed out that the behavior for various backends is
>       +    often more happenstance than design.  The specific change made in that
>       +    commit is actually quite relevant as well and much of the logic there
>       +    directly applies here.

Thanks for rewording this I think the new version is better for omitting 
the reference to the interactive backend. The interdiff for this patch 
and the next look good.

Thanks for working on this - it's turned into quite a large series

Best Wishes

Phillip

>            It makes a lot of sense in 'git commit' to error out on the creation of
>            empty commits, unless an override flag is provided.  However, once
>       @@ -405,8 +405,6 @@
>        +	test_cmp expect actual
>        +'
>        +
>       -+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>       -+
>        +test_expect_success 'rebase --interactive with a variety of empty commits' '
>        +	git checkout -B testing localmods &&
>        +	test_must_fail git rebase --interactive upstream &&
>    4:  c9542a2abe0 !  4:  54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty
>       @@ -98,6 +98,8 @@
>        +	With ask (implied by --interactive), the rebase will halt when
>        +	an empty commit is applied allowing you to choose whether to
>        +	drop it, edit files more, or just commit the empty changes.
>       ++	Other options, like --exec, will use the default of drop unless
>       ++	-i/--interactive is explicitly specified.
>        ++
>        +Note that commits which start empty are kept, and commits which are
>        +clean cherry-picks (as determined by `git log --cherry-mark ...`) are
>       @@ -224,7 +226,7 @@
>         			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
>         		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
>        +		OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
>       -+			       N_("how to handle empty commits"),
>       ++			       N_("how to handle commits that become empty"),
>        +			       PARSE_OPT_NONEG, parse_opt_empty),
>         		{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
>         			N_("(DEPRECATED) keep empty commits"),
>       @@ -342,8 +344,10 @@
>        -	if (!opts->no_commit) {
>        +	} else if (allow == 2) {
>        +		drop_commit = 1;
>       -+		fprintf(stderr, _("No changes -- Patch already applied.\n"));
>       -+	} // else allow == 0 and there's nothing special to do
>       ++		fprintf(stderr,
>       ++			_("dropping %s %s -- patch contents already upstream\n"),
>       ++			oid_to_hex(&commit->object.oid), msg.subject);
>       ++	} /* else allow == 0 and there's nothing special to do */
>        +	if (!opts->no_commit && !drop_commit) {
>         		if (author || command == TODO_REVERT || (flags & AMEND_MSG))
>         			res = do_commit(r, msg_file, author, opts, flags);
>       @@ -404,9 +408,7 @@
>        -test_expect_failure 'rebase --merge with a variety of empty commits' '
>        -	test_when_finished "git rebase --abort" &&
>        +test_expect_success 'rebase --merge --empty=drop' '
>       - 	git checkout -B testing localmods &&
>       --	# rebase --merge should not halt on the commit that becomes empty
>       --	git rebase --merge upstream &&
>       ++	git checkout -B testing localmods &&
>        +	git rebase --merge --empty=drop upstream &&
>        +
>        +	test_write_lines D C B A >expect &&
>       @@ -414,6 +416,17 @@
>        +	test_cmp expect actual
>        +'
>        +
>       ++test_expect_success 'rebase --merge uses default of --empty=drop' '
>       + 	git checkout -B testing localmods &&
>       +-	# rebase --merge should not halt on the commit that becomes empty
>       + 	git rebase --merge upstream &&
>       +
>       + 	test_write_lines D C B A >expect &&
>       +@@
>       + 	test_cmp expect actual
>       + '
>       +
>       +-test_expect_success 'rebase --interactive with a variety of empty commits' '
>        +test_expect_success 'rebase --merge --empty=keep' '
>        +	git checkout -B testing localmods &&
>        +	git rebase --merge --empty=keep upstream &&
>       @@ -428,14 +441,12 @@
>        +	test_must_fail git rebase --merge --empty=ask upstream &&
>        +
>        +	git rebase --skip &&
>       -
>       - 	test_write_lines D C B A >expect &&
>       - 	git log --format=%s >actual &&
>       -@@
>       -
>       - GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
>       -
>       --test_expect_success 'rebase --interactive with a variety of empty commits' '
>       ++
>       ++	test_write_lines D C B A >expect &&
>       ++	git log --format=%s >actual &&
>       ++	test_cmp expect actual
>       ++'
>       ++
>        +test_expect_success 'rebase --interactive --empty=drop' '
>        +	git checkout -B testing localmods &&
>        +	git rebase --interactive --empty=drop upstream &&
>       @@ -455,11 +466,19 @@
>        +'
>        +
>        +test_expect_success 'rebase --interactive --empty=ask' '
>       - 	git checkout -B testing localmods &&
>       --	test_must_fail git rebase --interactive upstream &&
>       ++	git checkout -B testing localmods &&
>        +	test_must_fail git rebase --interactive --empty=ask upstream &&
>       -
>       - 	git rebase --skip &&
>       ++
>       ++	git rebase --skip &&
>       ++
>       ++	test_write_lines D C B A >expect &&
>       ++	git log --format=%s >actual &&
>       ++	test_cmp expect actual
>       ++'
>       ++
>       ++test_expect_success 'rebase --interactive uses default of --empty=ask' '
>       + 	git checkout -B testing localmods &&
>       + 	test_must_fail git rebase --interactive upstream &&
>         
>        
>         diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
>    5:  9f66229d5cc =  5:  f5170e71510 t3406: simplify an already simple test
>    6:  8d731fa39c3 =  6:  2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling
>    7:  b6b6597eef8 =  7:  3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer
>    8:  0acefa988b1 =  8:  4e6f5ce8c46 rebase: fix handling of restrict_revision
>    9:  8c5b5b51330 =  9:  bcd04ec4ded t3432: make these tests work with either am or merge backends
>   10:  b8c087d6fb1 = 10:  45eb87b36b0 rebase: allow more types of rebases to fast-forward
>   11:  b50a1741e0b = 11:  11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends
>   12:  58e6e4ffb3f = 12:  0c921919dce rebase: move incompatibility checks between backend options a bit earlier
>   13:  5478c730aca = 13:  de1b0aaca88 rebase: add an --am option
>   14:  db5e29bd818 = 14:  e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases
>   15:  413e190ac9a ! 15:  f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases
>       @@ -54,17 +54,6 @@
>         		 */
>         		return error(_("%s: Unable to write new index file"),
>         			_(action_name(opts)));
>       -@@
>       - 	int next = todo_list->current, offset, fd;
>       -
>       - 	/*
>       --	 * rebase -i writes "git-rebase-todo" without the currently executing
>       --	 * command, appending it to "done" instead.
>       -+	 * interactive backend writes "git-rebase-todo" without the currently
>       -+	 * executing command, appending it to "done" instead.
>       - 	 */
>       - 	if (is_rebase_i(opts))
>       - 		next++;
>        @@
>         		return error(_("illegal label name: '%.*s'"), len, name);
>         
>   16:  170be283a85 ! 16:  56486d4d694 rebase tests: mark tests specific to the am-backend with --am
>       @@ -173,6 +173,163 @@
>         testrebase " --interactive" .git/rebase-merge
>         
>        
>       + diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
>       + --- a/t/t3421-rebase-topology-linear.sh
>       + +++ b/t/t3421-rebase-topology-linear.sh
>       +@@
>       + 		test_linear_range 'd e' c..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_cmp_rev e HEAD
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'd e' b..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success --fork-point
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       +@@
>       + 		test_linear_range 'd e' branch-b..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success --fork-point
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       +@@
>       + 		test_cmp_rev e HEAD
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success --fork-point
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       +@@
>       + 		test_linear_range 'd i' h..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'd' h..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'd i' f..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'd gp i' h..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'j d k l' c..
>       + 	"
>       + }
>       +-test_run_rebase failure ''
>       ++test_run_rebase failure --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase failure -p
>       +@@
>       + 		test_linear_range 'd k l' c..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'd k l' j..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'x y' c..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 		test_linear_range 'x y' c..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase failure -p
>       +@@
>       + 		test_linear_range 'x y' m..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase success -p
>       +@@
>       + 	"
>       + }
>       +
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase failure -p
>       +@@
>       + 		test_linear_range 'x y' m..
>       + 	"
>       + }
>       +-test_run_rebase success ''
>       ++test_run_rebase success --am
>       + test_run_rebase success -m
>       + test_run_rebase success -i
>       + test_have_prereq !REBASE_P || test_run_rebase failure -p
>       +
>         diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh
>         --- a/t/t3425-rebase-topology-merges.sh
>         +++ b/t/t3425-rebase-topology-merges.sh
>   17:  1e3d4066c40 = 17:  dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am
>   18:  9b4ac83d2d0 = 18:  ba1f2699b39 rebase: make the backend configurable via config setting
>   19:  859a4a94d7f ! 19:  59faaa2f920 rebase: change the default backend from "am" to "merge"
>       @@ -13,6 +13,7 @@
>                patches incorrectly.[2]
>              * lack of access to original commits means that conflict marker
>                annotation has less information available.
>       +      * the am backend has safety problems with an ill-timed interrupt.
>        
>            Also, the merge/interactive backend have far more abilities, appear to
>            currently have a slight performance advantage[3] and have room for more
>       @@ -38,6 +39,24 @@
>         +
>         Note that a rebase merge works by replaying each commit from the working
>         branch on top of the <upstream> branch.  Because of this, when a merge
>       +@@
>       + should have the same behavior, though it is not clear which one is
>       + correct.
>       +
>       ++Interruptability
>       ++~~~~~~~~~~~~~~~~
>       ++
>       ++The am backend has safety problems with an ill-timed interrupt; if the
>       ++user presses Ctrl-C at the wrong time to try to abort the rebase, the
>       ++rebase can enter a state where it cannot be aborted with a subsequent
>       ++`git rebase --abort`.  The interactive backend does not appear to
>       ++suffer from the same shortcoming.  (See
>       ++https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for
>       ++details.)
>       ++
>       + Miscellaneous differences
>       + ~~~~~~~~~~~~~~~~~~~~~~~~~
>       +
>        
>         diff --git a/builtin/rebase.c b/builtin/rebase.c
>         --- a/builtin/rebase.c
>    -:  ----------- > 20:  ad8339aebf2 rebase: rename the two primary rebase backends
> 

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-02-15 21:36         ` [PATCH v5 20/20] rebase: rename the two primary rebase backends Elijah Newren via GitGitGadget
@ 2020-03-12 15:13           ` Emily Shaffer
  2020-03-12 16:33             ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Emily Shaffer @ 2020-03-12 15:13 UTC (permalink / raw)
  To: Elijah Newren via GitGitGadget
  Cc: git, Johannes.Schindelin, phillip.wood, liu.denton, gitster,
	plroskin, alban.gruin, szeder.dev, jrnieder, Elijah Newren

On Sat, Feb 15, 2020 at 09:36:41PM +0000, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@gmail.com>
> 
> Two related changes, with separate rationale for each:
> 
> Rename the 'interactive' backend to 'merge' because:
>   * 'interactive' as a name caused confusion; this backend has been used
>     for many kinds of non-interactive rebases, and will probably be used
>     in the future for more non-interactive rebases than interactive ones
>     given that we are making it the default.
>   * 'interactive' is not the underlying strategy; merging is.
>   * the directory where state is stored is not called
>     .git/rebase-interactive but .git/rebase-merge.
> 
> Rename the 'am' backend to 'apply' because:
>   * Few users are familiar with git-am as a reference point.
>   * Related to the above, the name 'am' makes sentences in the
>     documentation harder for users to read and comprehend (they may read
>     it as the verb from "I am"); avoiding this difficult places a large
>     burden on anyone writing documentation about this backend to be very
>     careful with quoting and sentence structure and often forces
>     annoying redundancy to try to avoid such problems.
>   * Users stumble over pronunciation ("am" as in "I am a person not a
>     backend" or "am" as in "the first and thirteenth letters in the
>     alphabet in order are "A-M"); this may drive confusion when one user
>     tries to explain to another what they are doing.
>   * While "am" is the tool driving this backend, the tool driving git-am
>     is git-apply, and since we are driving towards lower-level tools
>     for the naming of the merge backend we may as well do so here too.
>   * The directory where state is stored has never been called
>     .git/rebase-am, it was always called .git/rebase-apply.
> 
> For all the reasons listed above:
>   * Modify the documentation to refer to the backends with the new names
>   * Provide a brief note in the documentation connecting the new names
>     to the old names in case users run across the old names anywhere
>     (e.g. in old release notes or older versions of the documentation)
>   * Change the (new) --am command line flag to --apply
>   * Rename some enums, variables, and functions to reinforce the new
>     backend names for us as well.
> 
> Signed-off-by: Elijah Newren <newren@gmail.com>

Hi,

This broke quite a few upstream users for us today when we rolled out a
next with this patch added on top. To shim around the post-commit hook
issue, we had set a system config for all our users to use
merge.backend=am; the machinery is pretty intolerant to a wrongly
configured backend name (die() rather than a warning and fallback).

Would it make more sense to deal with an unrecognized backend by falling
back to the default backend, instead?

>  	if (options.type == REBASE_UNSPECIFIED) {
>  		if (!strcmp(options.default_backend, "merge"))
> -			imply_interactive(&options, "--merge");
> -		else if (!strcmp(options.default_backend, "am"))
> -			options.type = REBASE_AM;
> +			imply_merge(&options, "--merge");
> +		else if (!strcmp(options.default_backend, "apply"))
> +			options.type = REBASE_APPLY;
>  		else
>  			die(_("Unknown rebase backend: %s"),
>  			    options.default_backend);

At the very least, can this die() explain that it found that string in
the config so the user can have a guess as to how to fix it?

(I realize the complained code is from earlier in the series, but this
patch - renaming something that used to be valid without a fallback -
invalidated our configs, highlighting the problem for us. So I'm
replying here instead.)

 - Emily


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 15:13           ` Emily Shaffer
@ 2020-03-12 16:33             ` Elijah Newren
  2020-03-12 17:55               ` Jonathan Nieder
                                 ` (2 more replies)
  0 siblings, 3 replies; 161+ messages in thread
From: Elijah Newren @ 2020-03-12 16:33 UTC (permalink / raw)
  To: Emily Shaffer
  Cc: Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:
>
> On Sat, Feb 15, 2020 at 09:36:41PM +0000, Elijah Newren via GitGitGadget wrote:
> > From: Elijah Newren <newren@gmail.com>
> >
> > Two related changes, with separate rationale for each:
> >
> > Rename the 'interactive' backend to 'merge' because:
> >   * 'interactive' as a name caused confusion; this backend has been used
> >     for many kinds of non-interactive rebases, and will probably be used
> >     in the future for more non-interactive rebases than interactive ones
> >     given that we are making it the default.
> >   * 'interactive' is not the underlying strategy; merging is.
> >   * the directory where state is stored is not called
> >     .git/rebase-interactive but .git/rebase-merge.
> >
> > Rename the 'am' backend to 'apply' because:
> >   * Few users are familiar with git-am as a reference point.
> >   * Related to the above, the name 'am' makes sentences in the
> >     documentation harder for users to read and comprehend (they may read
> >     it as the verb from "I am"); avoiding this difficult places a large
> >     burden on anyone writing documentation about this backend to be very
> >     careful with quoting and sentence structure and often forces
> >     annoying redundancy to try to avoid such problems.
> >   * Users stumble over pronunciation ("am" as in "I am a person not a
> >     backend" or "am" as in "the first and thirteenth letters in the
> >     alphabet in order are "A-M"); this may drive confusion when one user
> >     tries to explain to another what they are doing.
> >   * While "am" is the tool driving this backend, the tool driving git-am
> >     is git-apply, and since we are driving towards lower-level tools
> >     for the naming of the merge backend we may as well do so here too.
> >   * The directory where state is stored has never been called
> >     .git/rebase-am, it was always called .git/rebase-apply.
> >
> > For all the reasons listed above:
> >   * Modify the documentation to refer to the backends with the new names
> >   * Provide a brief note in the documentation connecting the new names
> >     to the old names in case users run across the old names anywhere
> >     (e.g. in old release notes or older versions of the documentation)
> >   * Change the (new) --am command line flag to --apply
> >   * Rename some enums, variables, and functions to reinforce the new
> >     backend names for us as well.
> >
> > Signed-off-by: Elijah Newren <newren@gmail.com>
>
> Hi,
>
> This broke quite a few upstream users for us today when we rolled out a
> next with this patch added on top. To shim around the post-commit hook
> issue, we had set a system config for all our users to use
> merge.backend=am; the machinery is pretty intolerant to a wrongly
> configured backend name (die() rather than a warning and fallback).
>
> Would it make more sense to deal with an unrecognized backend by falling
> back to the default backend, instead?
>
> >       if (options.type == REBASE_UNSPECIFIED) {
> >               if (!strcmp(options.default_backend, "merge"))
> > -                     imply_interactive(&options, "--merge");
> > -             else if (!strcmp(options.default_backend, "am"))
> > -                     options.type = REBASE_AM;
> > +                     imply_merge(&options, "--merge");
> > +             else if (!strcmp(options.default_backend, "apply"))
> > +                     options.type = REBASE_APPLY;
> >               else
> >                       die(_("Unknown rebase backend: %s"),
> >                           options.default_backend);
>
> At the very least, can this die() explain that it found that string in
> the config so the user can have a guess as to how to fix it?
>
> (I realize the complained code is from earlier in the series, but this
> patch - renaming something that used to be valid without a fallback -
> invalidated our configs, highlighting the problem for us. So I'm
> replying here instead.)

Sorry for the pain.  The earlier part of the series had only ever made
it to next, and was reverted there, and thus, in my thinking, in the
new cycle no one would have ever seen the intermediate state.  (Oops,
I forgot about cases where people tried out next towards the end of
last cycle before it was reverted and decided to set config based upon
it.)

I'm a little worried about ignoring the setting and just picking one;
if the setting has been marked and they set it to e.g. "appply" (one
too many p's), then does it really make sense to just show a warning
but continue using the backend they didn't want, especially since they
may miss the warning among the rest of the output?  I'd rather go the
route of improving the message, perhaps:
        _("Unknown rebase.backend config setting: %s")
Would that work for you?

Also, I thought that you and Jonathan were going to be changing the
post-commit hook handling[1][2].  Does this mean that you've punted on
that, and I need to make some kind of change here to get you to switch
over?

Elijah

[1] https://lore.kernel.org/git/pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com/
[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 16:33             ` Elijah Newren
@ 2020-03-12 17:55               ` Jonathan Nieder
  2020-03-12 18:39                 ` Elijah Newren
  2020-03-12 19:07               ` Junio C Hamano
  2020-03-12 19:12               ` Junio C Hamano
  2 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-03-12 17:55 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren wrote:
> On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:

>> This broke quite a few upstream users for us today when we rolled out a
>> next with this patch added on top. To shim around the post-commit hook
>> issue, we had set a system config for all our users to use
>> merge.backend=am; the machinery is pretty intolerant to a wrongly
>> configured backend name (die() rather than a warning and fallback).
>>
>> Would it make more sense to deal with an unrecognized backend by falling
>> back to the default backend, instead?
[...]
> Sorry for the pain.  The earlier part of the series had only ever made
> it to next, and was reverted there, and thus, in my thinking, in the
> new cycle no one would have ever seen the intermediate state.  (Oops,
> I forgot about cases where people tried out next towards the end of
> last cycle before it was reverted and decided to set config based upon
> it.)

Right, I'm mostly interested in this for the future: do we expect the
list of backends to only grow over time, or do we want to support
removing and renaming backends?  In the latter case, how can we support
people sharing config between multiple Git versions with their
merge.backend settings?

> I'm a little worried about ignoring the setting and just picking one;
> if the setting has been marked and they set it to e.g. "appply" (one
> too many p's), then does it really make sense to just show a warning
> but continue using the backend they didn't want, especially since they
> may miss the warning among the rest of the output?  I'd rather go the
> route of improving the message, perhaps:
>         _("Unknown rebase.backend config setting: %s")
> Would that work for you?

What if we support multiple merge.backend values, with semantics:

- last recognized value wins
- if no value is specified, use the default
- if values are specified but none are recognized, error out with a
  clear error message

?

> Also, I thought that you and Jonathan were going to be changing the
> post-commit hook handling[1][2].  Does this mean that you've punted on
> that, and I need to make some kind of change here to get you to switch
> over?

The setting was a stopgap; our interest in this upstream is primarily
from the point of view of "we ran into this, so let's take the
opportunity to help others that might run into similar issues in the
future".

Thanks,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 17:55               ` Jonathan Nieder
@ 2020-03-12 18:39                 ` Elijah Newren
  2020-03-12 18:46                   ` Jonathan Nieder
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-03-12 18:39 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Elijah Newren wrote:
> > On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:
>
> >> This broke quite a few upstream users for us today when we rolled out a
> >> next with this patch added on top. To shim around the post-commit hook
> >> issue, we had set a system config for all our users to use
> >> merge.backend=am; the machinery is pretty intolerant to a wrongly
> >> configured backend name (die() rather than a warning and fallback).
> >>
> >> Would it make more sense to deal with an unrecognized backend by falling
> >> back to the default backend, instead?
> [...]
> > Sorry for the pain.  The earlier part of the series had only ever made
> > it to next, and was reverted there, and thus, in my thinking, in the
> > new cycle no one would have ever seen the intermediate state.  (Oops,
> > I forgot about cases where people tried out next towards the end of
> > last cycle before it was reverted and decided to set config based upon
> > it.)
>
> Right, I'm mostly interested in this for the future: do we expect the
> list of backends to only grow over time, or do we want to support
> removing and renaming backends?  In the latter case, how can we support
> people sharing config between multiple Git versions with their
> merge.backend settings?

Good questions.  We have already stated that backends might be
"removed" or at least "ignored"; from Documentation/config/rebase.txt:

rebase.backend::
        Default backend to use for rebasing.  Possible choices are
        'apply' or 'merge'.  In the future, if the merge backend gains
        all remaining capabilities of the apply backend, this setting
        may become unused.

In other words, we're already telling users that 'apply' might mean
'merge' in future versions of git.  (Or at least that was my mental
model; perhaps I should tighten up the wording to state it that way?)

I hadn't thought in terms of adding backends, especially since we've
been trying to remove them, though given our history[1] maybe I should
have.  I guess it's possible we could introduce another backend[2],
though we don't necessarily have to make it possible to configure
every backend as the default.  For example, despite the technical
existence of another backend right now, I did not make
'preserve-merges' a possible setting since it is already deprecated.
(And to go on a bit of a tangent, I think that if someone is not in
the middle of a rebase and tries to start one with the -p flag, we
should just throw a warning and pretend they passed -r.  We should
kill preserve-merges as much as possible.  Anyway, enough of that
tangent...)

[1] By my count, we've had at least five rebase backends: apply/am,
merge-via-shell-out-to-merge-recursive-builtin,
legacy-interactive-via-shell,
interactive-as-builtin-and-now-called-merge, and preserve-merges.  I
think at one point all five co-existed.  And I wouldn't be surprised
if I've forgotten, overlooked, or otherwise missed others.

[2] Maybe I even started one with
https://github.com/newren/git/blob/git-merge-2020-demo/builtin/fast-rebase.c,
but unless it's just too hard to refactor sequencer.c &
builtin/rebase.c to make use of merge-ort (AND quit forking
subprocesses other than with --exec and -s AND avoid writing state
files unless and until you hit a conflict), I'm thinking that dies as
nothing more than a demo.

> > I'm a little worried about ignoring the setting and just picking one;
> > if the setting has been marked and they set it to e.g. "appply" (one
> > too many p's), then does it really make sense to just show a warning
> > but continue using the backend they didn't want, especially since they
> > may miss the warning among the rest of the output?  I'd rather go the
> > route of improving the message, perhaps:
> >         _("Unknown rebase.backend config setting: %s")
> > Would that work for you?
>
> What if we support multiple merge.backend values, with semantics:
>
> - last recognized value wins
> - if no value is specified, use the default
> - if values are specified but none are recognized, error out with a
>   clear error message
>
> ?

Sure, but...isn't that what we already do, other than maybe the
'clear' part of step 3?  ('merge' and 'apply' are recognized values.
If no value is specified, the default of merge is used.  If values are
specified but none are recognized, it errors out.)  Would my suggested
change above fix it so that we can consider it sufficiently clear, or
do you want something else?

> > Also, I thought that you and Jonathan were going to be changing the
> > post-commit hook handling[1][2].  Does this mean that you've punted on
> > that, and I need to make some kind of change here to get you to switch
> > over?
>
> The setting was a stopgap; our interest in this upstream is primarily
> from the point of view of "we ran into this, so let's take the
> opportunity to help others that might run into similar issues in the
> future".

Thanks for flagging it; I appreciate it.  Is my suggested rewording of
the error message helpful?  Have I missed or misunderstood anything
else you were trying to bring up?

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 18:39                 ` Elijah Newren
@ 2020-03-12 18:46                   ` Jonathan Nieder
  2020-03-12 19:31                     ` Elijah Newren
  2020-03-12 19:54                     ` Junio C Hamano
  0 siblings, 2 replies; 161+ messages in thread
From: Jonathan Nieder @ 2020-03-12 18:46 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Elijah Newren wrote:
> On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:

>> What if we support multiple merge.backend values, with semantics:
>>
>> - last recognized value wins
>> - if no value is specified, use the default
>> - if values are specified but none are recognized, error out with a
>>   clear error message
>>
>> ?
>
> Sure, but...isn't that what we already do, other than maybe the
> 'clear' part of step 3?

Sorry for the lack of clarity.  I mean allowing

	[rebase]
		backend = am
		backend = apply
		backend = futuristic

with behavior

- on "git" that understands am but not apply or futuristic, use the am
  backend
- on "git" that understands apply but not am or futuristic, use the
  apply backend
- on "git" that understands apply and futuristic, use the futuristic
  backend

That way, a single config file is usable on all three versions of Git.

Thanks,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 16:33             ` Elijah Newren
  2020-03-12 17:55               ` Jonathan Nieder
@ 2020-03-12 19:07               ` Junio C Hamano
  2020-03-12 19:12                 ` Jonathan Nieder
  2020-03-12 19:12               ` Junio C Hamano
  2 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-03-12 19:07 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin,
	Alban Gruin, SZEDER Gábor, Jonathan Nieder

Elijah Newren <newren@gmail.com> writes:

> I'm a little worried about ignoring the setting and just picking one;

I am more than a little worried, too.  I think erroring out is
warranted in this case for exactly the reason you gave here.

> if the setting has been marked and they set it to e.g. "appply" (one
> too many p's), then does it really make sense to just show a warning
> but continue using the backend they didn't want, especially since they
> may miss the warning among the rest of the output?  I'd rather go the
> route of improving the message, perhaps:
>         _("Unknown rebase.backend config setting: %s")


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 16:33             ` Elijah Newren
  2020-03-12 17:55               ` Jonathan Nieder
  2020-03-12 19:07               ` Junio C Hamano
@ 2020-03-12 19:12               ` Junio C Hamano
  2020-03-12 19:29                 ` Elijah Newren
  2 siblings, 1 reply; 161+ messages in thread
From: Junio C Hamano @ 2020-03-12 19:12 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Jeff King, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

Elijah Newren <newren@gmail.com> writes:

> On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:
>> ...
>> This broke quite a few upstream users for us today...

One more thing I've been wondering was if we should really have said
"ok, the two known gotchas have been dealt with, so let's ship it".
It is quite likely that users may be hit by remaining differences
between the merge and apply backends that we assume to be merely
subtle and easy-to-work-around ones, and it may be prudent to use
the "let's not change the default just yet, but ask people to test
it in their workflow by advertising the configuration" patch.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 19:07               ` Junio C Hamano
@ 2020-03-12 19:12                 ` Jonathan Nieder
  0 siblings, 0 replies; 161+ messages in thread
From: Jonathan Nieder @ 2020-03-12 19:12 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Elijah Newren, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi,

Junio C Hamano wrote:
> Elijah Newren <newren@gmail.com> writes:

>> I'm a little worried about ignoring the setting and just picking one;
>
> I am more than a little worried, too.  I think erroring out is
> warranted in this case for exactly the reason you gave here.

To avoid misunderstandings: have you read the proposal I made that aims
to avoid that problem?

Thanks,
Jonathan

>> if the setting has been marked and they set it to e.g. "appply" (one
>> too many p's), then does it really make sense to just show a warning
>> but continue using the backend they didn't want, especially since they
>> may miss the warning among the rest of the output?  I'd rather go the
>> route of improving the message, perhaps:
>>         _("Unknown rebase.backend config setting: %s")

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 19:12               ` Junio C Hamano
@ 2020-03-12 19:29                 ` Elijah Newren
  2020-03-12 20:37                   ` Jeff King
  0 siblings, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-03-12 19:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Jeff King, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

On Thu, Mar 12, 2020 at 12:12 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Elijah Newren <newren@gmail.com> writes:
>
> > On Thu, Mar 12, 2020 at 8:13 AM Emily Shaffer <emilyshaffer@google.com> wrote:
> >> ...
> >> This broke quite a few upstream users for us today...
>
> One more thing I've been wondering was if we should really have said
> "ok, the two known gotchas have been dealt with, so let's ship it".
> It is quite likely that users may be hit by remaining differences
> between the merge and apply backends that we assume to be merely
> subtle and easy-to-work-around ones, and it may be prudent to use
> the "let's not change the default just yet, but ask people to test
> it in their workflow by advertising the configuration" patch.

Good question, but there's several things to unpack here...

First, note that this particular breakage would have occurred
regardless of the default setting, because the problem was that they
setting rebase.backend to an unrecognized value, not that we used a
different backend than they were used to.

Second, for the particular case of the post-commit hook that they
referenced in their rationale, that item was documented near the end
of the 2.25 cycle and mentioned in previous patchsets[1, 2] so I
figured this case was already considered.

Those two points may obscure the issue, though; your question is still
valid.  I think the bigger question is whether there are other unknown
differences, or even known differences that are a bigger issue than we
currently realize.  That's hard to judge, and it may not be possible
to judge until we've flipped the default.  As such, it's a judgement
call.  I can see the judgement call going either way.  A couple things
to weigh in on how to go:
   - Making rebase.backend default to 'apply' for 2.26 is certainly
the conservative option and may give us more feedback and time to iron
out differences
   - We had multiple complaints this cycle about rebase.backend=apply
merging things incorrectly with the only workaround being to use the
merge backend[3,4]
   - The rebase-backend topic wasn't merged down to master until less
than a week before -rc0.  (For a variety of reasons.)  A big change
like this probably would have been better to merge down earlier in
some cycle.

To be honest, if I was maintainer, I'm not sure which direction I'd
pick.  If you feel safer switching the default backend to apply for
this cycle and then (re-)revert it early next cycle to the merge
backend, I think that's totally reasonable.


[1] https://lore.kernel.org/git/pull.679.v4.git.git.1579155273.gitgitgadget@gmail.com/
[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/
[3] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[4] https://lore.kernel.org/git/20200108223557.GE32750@szeder.dev/

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 18:46                   ` Jonathan Nieder
@ 2020-03-12 19:31                     ` Elijah Newren
  2020-03-17  2:58                       ` Jonathan Nieder
  2020-03-12 19:54                     ` Junio C Hamano
  1 sibling, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-03-12 19:31 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Elijah Newren wrote:
> > On Thu, Mar 12, 2020 at 10:55 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> >> What if we support multiple merge.backend values, with semantics:
> >>
> >> - last recognized value wins
> >> - if no value is specified, use the default
> >> - if values are specified but none are recognized, error out with a
> >>   clear error message
> >>
> >> ?
> >
> > Sure, but...isn't that what we already do, other than maybe the
> > 'clear' part of step 3?
>
> Sorry for the lack of clarity.  I mean allowing
>
>         [rebase]
>                 backend = am
>                 backend = apply
>                 backend = futuristic
>
> with behavior
>
> - on "git" that understands am but not apply or futuristic, use the am
>   backend
> - on "git" that understands apply but not am or futuristic, use the
>   apply backend
> - on "git" that understands apply and futuristic, use the futuristic
>   backend
>
> That way, a single config file is usable on all three versions of Git.

Ah, gotcha, that makes sense though we'd need to make the thing
multi-valued which is a bit late for 2.26.  But we could at least
extend the logic in that way for 2.27.  Also, to further clarify, if
they have

         [rebase]
                 backend = misspelled
                 backend = futuristic

then what should the error be?  A couple possibilities:

fatal: Unknown rebase backend: futuristic

fatal: Unknown rebase.backend config setting: futuristic

fatal: Unknown rebase.backend config setting; valid values are 'merge', 'apply'.

or should it be something else?

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 18:46                   ` Jonathan Nieder
  2020-03-12 19:31                     ` Elijah Newren
@ 2020-03-12 19:54                     ` Junio C Hamano
  1 sibling, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2020-03-12 19:54 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Elijah Newren, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Jonathan Nieder <jrnieder@gmail.com> writes:

> Sorry for the lack of clarity.  I mean allowing
>
> 	[rebase]
> 		backend = am
> 		backend = apply
> 		backend = futuristic
>
> with behavior
>
> - on "git" that understands am but not apply or futuristic, use the am
>   backend
> - on "git" that understands apply but not am or futuristic, use the
>   apply backend
> - on "git" that understands apply and futuristic, use the futuristic
>   backend
>
> That way, a single config file is usable on all three versions of Git.

IOW, make rebase.backend a multi-valued one and use it when one and
only one value is a recognised one?  What would an error message say
when none is recognised, and when would that be issued?

I like the general direction it takes, but I think it would be
simpler to (1) die when we see what we do not recognise and (2)
teach the latest one that "am" is a synonym for "apply", without
even advertising "am".


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 19:29                 ` Elijah Newren
@ 2020-03-12 20:37                   ` Jeff King
  2020-03-12 21:27                     ` Junio C Hamano
  0 siblings, 1 reply; 161+ messages in thread
From: Jeff King @ 2020-03-12 20:37 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Junio C Hamano, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

On Thu, Mar 12, 2020 at 12:29:45PM -0700, Elijah Newren wrote:

> First, note that this particular breakage would have occurred
> regardless of the default setting, because the problem was that they
> setting rebase.backend to an unrecognized value, not that we used a
> different backend than they were used to.

If I understand correctly, it was also a setting that never worked in
any released version of Git. It was magic that was only ever in 'next'.

As annoying as it is to experience breakage, I'm not _too_ sympathetic
to this case, because that is part of the cost of running the bleeding
edge of 'next' or even 'master'. I.e., I think we have to make a cutoff
_somewhere_ to say "this is something that made it to the general
public, and therefore we can't break backwards compatibility" to keep
our sanity during development. And it seems like tagged releases are a
pretty good cutoff to me.

Though in this particular case, I don't mind too much just leaving "am"
as an alias for "apply" (it was actually the first thing I tried when
writing my earlier emails, but I'm probably not a representative user
there). Putting that in a release, though, may mean supporting it
forever. :)

>    - We had multiple complaints this cycle about rebase.backend=apply
> merging things incorrectly with the only workaround being to use the
> merge backend[3,4]
>    - The rebase-backend topic wasn't merged down to master until less
> than a week before -rc0.  (For a variety of reasons.)  A big change
> like this probably would have been better to merge down earlier in
> some cycle.

It did feel a bit quick to me, hitting near the end of the cycle. We've
had the apply backend as the default for a decade, so even if there are
problems with it, they're known issues. So I don't think there's a
particular hurry. I'm not entirely convinced that cooking it longer
during the next cycle will turn up a lot of new data (I did find a few
issues, but the real test is the long-tail of weird use cases that we
won't see until an actual release). But it probably doesn't hurt much to
take it slow; it just delays a few bug-fixes (which people can still get
by setting a config option).

I guess like your email I'm going back and forth between the two
options. I think that means it probably doesn't matter _too_ much either
way.

-Peff

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 20:37                   ` Jeff King
@ 2020-03-12 21:27                     ` Junio C Hamano
  2020-03-12 22:06                       ` Elijah Newren
  2020-03-12 23:30                       ` Jonathan Nieder
  0 siblings, 2 replies; 161+ messages in thread
From: Junio C Hamano @ 2020-03-12 21:27 UTC (permalink / raw)
  To: Jeff King
  Cc: Elijah Newren, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

Jeff King <peff@peff.net> writes:

> Though in this particular case, I don't mind too much just leaving "am"
> as an alias for "apply" (it was actually the first thing I tried when
> writing my earlier emails, but I'm probably not a representative user
> there). Putting that in a release, though, may mean supporting it
> forever. :)

Yeah, I agree that what used to exist only in 'next' and we chose to
remove it before it its 'master', does not deserve to be supported
forever.  So let's scrap the "am is taken as a synonym for apply".
It would not help the old version taken from 'next' grok a new
configuration file that uses "apply" anyway ;-)

> I guess like your email I'm going back and forth between the two
> options. I think that means it probably doesn't matter _too_ much either
> way.

OK, let's avoid screwing it up even further by doing no more damage
than merging just the three fix-ups we discussed recently.  

The fact that jrnieder runs 'next'+patches for his $DAYJOB users
makes me hope that there may be other organizations that do the
same, and cooking in 'next' would mean somthing, though.

We may want to think of a way to strongly encourage those who are in
charge of choosing and maintaining the versions of Git that is used
in their organization, whose operation depends on the healthy future
versions of Git, to run 'next' or at least 'master', to stay ahead
of the released versions.  Some education and advocacy is needed?


^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 21:27                     ` Junio C Hamano
@ 2020-03-12 22:06                       ` Elijah Newren
  2020-03-13  0:04                         ` Junio C Hamano
  2020-03-12 23:30                       ` Jonathan Nieder
  1 sibling, 1 reply; 161+ messages in thread
From: Elijah Newren @ 2020-03-12 22:06 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

On Thu, Mar 12, 2020 at 2:27 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Jeff King <peff@peff.net> writes:
>
> > Though in this particular case, I don't mind too much just leaving "am"
> > as an alias for "apply" (it was actually the first thing I tried when
> > writing my earlier emails, but I'm probably not a representative user
> > there). Putting that in a release, though, may mean supporting it
> > forever. :)
>
> Yeah, I agree that what used to exist only in 'next' and we chose to
> remove it before it its 'master', does not deserve to be supported
> forever.  So let's scrap the "am is taken as a synonym for apply".
> It would not help the old version taken from 'next' grok a new
> configuration file that uses "apply" anyway ;-)
>
> > I guess like your email I'm going back and forth between the two
> > options. I think that means it probably doesn't matter _too_ much either
> > way.
>
> OK, let's avoid screwing it up even further by doing no more damage
> than merging just the three fix-ups we discussed recently.
>
> The fact that jrnieder runs 'next'+patches for his $DAYJOB users
> makes me hope that there may be other organizations that do the
> same, and cooking in 'next' would mean somthing, though.
>
> We may want to think of a way to strongly encourage those who are in
> charge of choosing and maintaining the versions of Git that is used
> in their organization, whose operation depends on the healthy future
> versions of Git, to run 'next' or at least 'master', to stay ahead
> of the released versions.  Some education and advocacy is needed?

I agree, it's super cool that Emily and Jonathan distribute 'next' to
their users at Google and provide us lots of early feedback.  I wish
we had something similar; currently, the only control I have is
requesting that some documentation file that includes a recommended
minimum git version be bumped to something newer, and I usually need
to provide a reason (I can't just say, "It's the newest release").
Other than that, developers install whatever version of git they like,
possibly unaware of what's in that documentation file.

In fact, not having a way to control git versions is leading to
discussions of spending my time to not only fix issues in git (e.g.
the read-tree -mu HEAD problems with sparse-checkout) but also writing
some separate program that does that piece independent of git so that
we can work with a variety of git versions.  Such a pain...does anyone
know how to educate me to advocate to the company that we come up with
some more controlled git version?

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 21:27                     ` Junio C Hamano
  2020-03-12 22:06                       ` Elijah Newren
@ 2020-03-12 23:30                       ` Jonathan Nieder
  1 sibling, 0 replies; 161+ messages in thread
From: Jonathan Nieder @ 2020-03-12 23:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, Elijah Newren, Emily Shaffer,
	Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Pavel Roskin,
	Alban Gruin, SZEDER Gábor

Junio C Hamano wrote:

>           So let's scrap the "am is taken as a synonym for apply".
> It would not help the old version taken from 'next' grok a new
> configuration file that uses "apply" anyway ;-)

Agreed.  For 2.26.0, I'm happy with either

- what we currently have, where it defaults to 'merge', or
- the more conservative approach where it supports 'merge' but defaults
  to 'apply'

Accepting multiple values for forward compatibility is an optional
cherry on top: I would like us to eventually get there, but I don't
mind if it doesn't make 2.26.0, and it's probably better to give a
change like that some cooking time.  (Although I won't complain if it
does make 2.26.0. ;-))

[...]
> We may want to think of a way to strongly encourage those who are in
> charge of choosing and maintaining the versions of Git that is used
> in their organization, whose operation depends on the healthy future
> versions of Git, to run 'next' or at least 'master', to stay ahead
> of the released versions.  Some education and advocacy is needed?

It's possible we should write up some best practices somewhere in
Documentation/ about how to make running "next" go smoothly:

- have a responsive infrastructure team.  Pay attention to changes
  landing upstream and have the infra team test before the rest of
  your user population :)

- if you have a large user population, use gradual rollouts so that a
  subset of users can find problematic changes before they affect
  everyone

- fast rollbacks

- telemetry to catch regressions (in latency, for example) when people
  are too shy to report them

We can also advertise places, such as Debian experimental, that people
can get snapshots without maintaining them themselves.

Thanks,
Jonathan

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 22:06                       ` Elijah Newren
@ 2020-03-13  0:04                         ` Junio C Hamano
  0 siblings, 0 replies; 161+ messages in thread
From: Junio C Hamano @ 2020-03-13  0:04 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Jeff King, Emily Shaffer, Elijah Newren via GitGitGadget,
	Git Mailing List, Johannes Schindelin, Phillip Wood, Denton Liu,
	Pavel Roskin, Alban Gruin, SZEDER Gábor, Jonathan Nieder

Elijah Newren <newren@gmail.com> writes:

>> We may want to think of a way to strongly encourage those who are in
>> charge of choosing and maintaining the versions of Git that is used
>> in their organization, whose operation depends on the healthy future
>> versions of Git, to run 'next' or at least 'master', to stay ahead
>> of the released versions.  Some education and advocacy is needed?
>
> I agree, it's super cool that Emily and Jonathan distribute 'next' to
> their users at Google and provide us lots of early feedback.  I wish
> we had something similar; currently, the only control I have is
> requesting that some documentation file that includes a recommended
> minimum git version be bumped to something newer, and I usually need
> to provide a reason (I can't just say, "It's the newest release").

Of course, within such an environment, "It's to help make sure that
the upcoming release won't be broken for us" is hard to phrase
in a palatable way for the management :-<.

^ permalink raw reply	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-12 19:31                     ` Elijah Newren
@ 2020-03-17  2:58                       ` Jonathan Nieder
  2020-03-17  4:45                         ` Elijah Newren
  0 siblings, 1 reply; 161+ messages in thread
From: Jonathan Nieder @ 2020-03-17  2:58 UTC (permalink / raw)
  To: Elijah Newren
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi,

Elijah Newren wrote:
> On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:

>> Sorry for the lack of clarity.  I mean allowing
>>
>>         [rebase]
>>                 backend = am
>>                 backend = apply
>>                 backend = futuristic
>>
>> with behavior
>>
>> - on "git" that understands am but not apply or futuristic, use the am
>>   backend
>> - on "git" that understands apply but not am or futuristic, use the
>>   apply backend
>> - on "git" that understands apply and futuristic, use the futuristic
>>   backend
>>
>> That way, a single config file is usable on all three versions of Git.
>
> Ah, gotcha, that makes sense though we'd need to make the thing
> multi-valued which is a bit late for 2.26.  But we could at least
> extend the logic in that way for 2.27.

Here's a patch implementing that.  I'm not convinced it's worth the
complexity, mostly because I'm not convinced that rebase is going to
have to select between additional new backends in the future.  But if
you think it will, then I think this would be a reasonable thing to do
(maybe even without the documentation part of the patch).

Thoughts?

Thanks,
Jonathan

-- >8 --
Subject: rebase: allow specifying unrecognized rebase.backend with a fallback

In 8295ed690bf (rebase: make the backend configurable via config
setting, 2020-02-15), Git learned a new rebase.backend setting that
can be used to specify which implementation should be used for
non-interactive rebases: "am" (now called "apply"), which uses "git
am", or "merge", which uses the three-way merge machinery.

Most likely those are the only two backends that rebase will ever need
to learn, so this level of configurability would be sufficient.  At
some point the "apply" backend would be retired, and the setting would
be removed altogether.

Suppose, though, that rebase learns another backend --- e.g. "faster".
In that case, a user might set configuration to request it:

	[rebase]
		backend = faster

If their configuration is shared between multiple versions of Git
(think "home directory on NFS shared between machines"), this would
produce errors when read by older versions of Git:

	fatal: Unknown rebase backend: faster

On the other hand, if we ignore unrecognized rebase backend settings,
then Git would fail to realize that

	[rebase]
		backend = appply

is a typo, producing a confusing user experience.  Let's do something
in between: when a rebase backend setting is unrecognized, fall back
to the last earlier recognized value, but if no value was recognized,
print an error message allowing the user to catch their typo.

Reported-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
 Documentation/config/rebase.txt |  5 ++
 builtin/rebase.c                | 52 +++++++++++++++---
 t/t3435-rebase-backend.sh       | 97 +++++++++++++++++++++++++++++++++
 3 files changed, 146 insertions(+), 8 deletions(-)
 create mode 100755 t/t3435-rebase-backend.sh

diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index 7f7a07d22f8..c92adbdcc69 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -10,6 +10,11 @@ rebase.backend::
 	'apply' or 'merge'.  In the future, if the merge backend gains
 	all remaining capabilities of the apply backend, this setting
 	may become unused.
++
+If set multiple times, the last value corresponding to a recognized
+backend is used. This is for forward compatibility, as it allows
+specifying a rebase backend that Git does not know about yet along
+with a backend known today as a fallback.
 
 rebase.stat::
 	Whether to show a diffstat of what changed upstream since the last
diff --git a/builtin/rebase.c b/builtin/rebase.c
index ffa467aad52..5b0fab9741f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -56,10 +56,18 @@ enum empty_type {
 	EMPTY_ASK
 };
 
+enum rebase_backend {
+	BACKEND_UNSPECIFIED = 0,
+	BACKEND_UNRECOGNIZED,
+	BACKEND_APPLY,
+	BACKEND_MERGE,
+};
+
 struct rebase_options {
 	enum rebase_type type;
 	enum empty_type empty;
-	const char *default_backend;
+	enum rebase_backend configured_backend;
+	const char *last_specified_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -100,7 +108,6 @@ struct rebase_options {
 #define REBASE_OPTIONS_INIT {			  	\
 		.type = REBASE_UNSPECIFIED,	  	\
 		.empty = EMPTY_UNSPECIFIED,	  	\
-		.default_backend = "merge",	  	\
 		.flags = REBASE_NO_QUIET, 		\
 		.git_am_opts = ARGV_ARRAY_INIT,		\
 		.git_format_patch_opt = STRBUF_INIT	\
@@ -1224,6 +1231,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	return status ? -1 : 0;
 }
 
+static enum rebase_backend parse_rebase_backend(const char *value)
+{
+	if (!strcmp(value, "apply"))
+		return BACKEND_APPLY;
+	if (!strcmp(value, "merge"))
+		return BACKEND_MERGE;
+	return BACKEND_UNRECOGNIZED;
+}
+
 static int rebase_config(const char *var, const char *value, void *data)
 {
 	struct rebase_options *opts = data;
@@ -1264,7 +1280,18 @@ static int rebase_config(const char *var, const char *value, void *data)
 	}
 
 	if (!strcmp(var, "rebase.backend")) {
-		return git_config_string(&opts->default_backend, var, value);
+		enum rebase_backend val;
+		if (!value)
+			return config_error_nonbool(var);
+		val = parse_rebase_backend(value);
+		if (opts->configured_backend == BACKEND_UNSPECIFIED)
+			opts->configured_backend = val;
+		else if (val == BACKEND_UNRECOGNIZED)
+			; /* Unrecognized rebase backend. Ignore it. */
+		else
+			opts->configured_backend = val;
+		opts->last_specified_backend = value;
+		return 0;
 	}
 
 	return git_default_config(var, value, data);
@@ -1903,14 +1930,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	if (options.configured_backend == BACKEND_UNRECOGNIZED)
+		die(_("unknown rebase backend: %s"),
+		    options.last_specified_backend);
+
 	if (options.type == REBASE_UNSPECIFIED) {
-		if (!strcmp(options.default_backend, "merge"))
+		switch (options.configured_backend) {
+		case BACKEND_UNSPECIFIED:
+		case BACKEND_MERGE:
 			imply_merge(&options, "--merge");
-		else if (!strcmp(options.default_backend, "apply"))
+			break;
+		case BACKEND_APPLY:
 			options.type = REBASE_APPLY;
-		else
-			die(_("Unknown rebase backend: %s"),
-			    options.default_backend);
+			break;
+		default:
+			BUG("unexpected backend %d",
+			    (int) options.configured_backend);
+		}
 	}
 
 	switch (options.type) {
diff --git a/t/t3435-rebase-backend.sh b/t/t3435-rebase-backend.sh
new file mode 100755
index 00000000000..8b9ba6f1894
--- /dev/null
+++ b/t/t3435-rebase-backend.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='rebase.backend tests
+
+Checks of config parsing for the [rebase] backend setting.  We detect
+which backend was used by checking which directory was created to hold
+state.'
+
+. ./test-lib.sh
+
+# usage: test_backend_choice <expectation> <command>
+#
+# Tests that the chosen backend for rebase command <command>
+# is <expectation> ("merge" or "apply").
+test_backend_choice () {
+	expect=$1 &&
+	shift &&
+
+	test_must_fail git "$@" master topic &&
+	case $expect in
+	apply)
+		test_path_is_dir .git/rebase-apply &&
+		test_path_is_missing .git/rebase-merge
+		;;
+	merge)
+		test_path_is_dir .git/rebase-merge &&
+		test_path_is_missing .git/rebase-apply
+		;;
+	*)
+		error "unrecognized expectation $expect"
+	esac
+}
+
+test_expect_success 'setup' '
+	test_commit base &&
+	test_commit sidea conflict.txt myway &&
+	git checkout -b topic base &&
+	test_commit sideb conflict.txt thehighway
+'
+
+test_expect_success '--apply uses apply backend' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice apply rebase --apply
+'
+
+test_expect_success '--merge uses merge backend' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice merge rebase --merge
+'
+
+test_expect_success 'default to merge backend' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice merge rebase
+'
+
+test_expect_success 'config overrides default' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice apply -c rebase.backend=apply rebase
+'
+
+test_expect_success 'option overrides config' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice merge -c rebase.backend=apply rebase --merge
+'
+
+test_expect_success 'last config value wins' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice merge \
+		-c rebase.backend=apply \
+		-c rebase.backend=merge \
+		rebase
+'
+
+test_expect_success 'last config value wins' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice merge \
+		-c rebase.backend=apply \
+		-c rebase.backend=merge \
+		rebase
+'
+
+test_expect_success 'misspelled backend without fallback is diagnosed' '
+	test_must_fail \
+		git -c rebase.backend=appply rebase master topic 2>message &&
+	test_i18ngrep "unknown rebase backend" message &&
+	grep appply message
+'
+
+test_expect_success 'forward compatibility by skipping unrecognized values' '
+	test_when_finished "git rebase --abort" &&
+	test_backend_choice apply \
+		-c rebase.backend=apply \
+		-c rebase.backend=futuristic \
+		rebase
+'
+
+test_done
-- 
2.25.1.481.gfbce0eb801


^ permalink raw reply related	[flat|nested] 161+ messages in thread

* Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
  2020-03-17  2:58                       ` Jonathan Nieder
@ 2020-03-17  4:45                         ` Elijah Newren
  0 siblings, 0 replies; 161+ messages in thread
From: Elijah Newren @ 2020-03-17  4:45 UTC (permalink / raw)
  To: Jonathan Nieder
  Cc: Emily Shaffer, Elijah Newren via GitGitGadget, Git Mailing List,
	Johannes Schindelin, Phillip Wood, Denton Liu, Junio C Hamano,
	Pavel Roskin, Alban Gruin, SZEDER Gábor

Hi Jonathan,

On Mon, Mar 16, 2020 at 7:58 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Hi,
>
> Elijah Newren wrote:
> > On Thu, Mar 12, 2020 at 11:46 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> >> Sorry for the lack of clarity.  I mean allowing
> >>
> >>         [rebase]
> >>                 backend = am
> >>                 backend = apply
> >>                 backend = futuristic
> >>
> >> with behavior
> >>
> >> - on "git" that understands am but not apply or futuristic, use the am
> >>   backend
> >> - on "git" that understands apply but not am or futuristic, use the
> >>   apply backend
> >> - on "git" that understands apply and futuristic, use the futuristic
> >>   backend
> >>
> >> That way, a single config file is usable on all three versions of Git.
> >
> > Ah, gotcha, that makes sense though we'd need to make the thing
> > multi-valued which is a bit late for 2.26.  But we could at least
> > extend the logic in that way for 2.27.
>
> Here's a patch implementing that.  I'm not convinced it's worth the
> complexity, mostly because I'm not convinced that rebase is going to
> have to select between additional new backends in the future.  But if
> you think it will, then I think this would be a reasonable thing to do
> (maybe even without the documentation part of the patch).
>
> Thoughts?

Thanks for investigating what's involved.  If there will be new rebase
backends, then this does look like nice future proofing to me.  As for
whether there will be...

Personally, I would rather decrease the number of backends than
increase, and if it was up to just me, I'd like to drive the number of
backends to one and then keep it there.  But it feels hard to know for
sure.

> Thanks,
> Jonathan
>
> -- >8 --
> Subject: rebase: allow specifying unrecognized rebase.backend with a fallback
>
> In 8295ed690bf (rebase: make the backend configurable via config
> setting, 2020-02-15), Git learned a new rebase.backend setting that
> can be used to specify which implementation should be used for
> non-interactive rebases: "am" (now called "apply"), which uses "git
> am", or "merge", which uses the three-way merge machinery.
>
> Most likely those are the only two backends that rebase will ever need
> to learn, so this level of configurability would be sufficient.  At
> some point the "apply" backend would be retired, and the setting would
> be removed altogether.
>
> Suppose, though, that rebase learns another backend --- e.g. "faster".
> In that case, a user might set configuration to request it:
>
>         [rebase]
>                 backend = faster
>
> If their configuration is shared between multiple versions of Git
> (think "home directory on NFS shared between machines"), this would
> produce errors when read by older versions of Git:
>
>         fatal: Unknown rebase backend: faster
>
> On the other hand, if we ignore unrecognized rebase backend settings,
> then Git would fail to realize that
>
>         [rebase]
>                 backend = appply
>
> is a typo, producing a confusing user experience.  Let's do something
> in between: when a rebase backend setting is unrecognized, fall back
> to the last earlier recognized value, but if no value was recognized,
> print an error message allowing the user to catch their typo.
>
> Reported-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
> ---
>  Documentation/config/rebase.txt |  5 ++
>  builtin/rebase.c                | 52 +++++++++++++++---
>  t/t3435-rebase-backend.sh       | 97 +++++++++++++++++++++++++++++++++
>  3 files changed, 146 insertions(+), 8 deletions(-)
>  create mode 100755 t/t3435-rebase-backend.sh
>
> diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
> index 7f7a07d22f8..c92adbdcc69 100644
> --- a/Documentation/config/rebase.txt
> +++ b/Documentation/config/rebase.txt
> @@ -10,6 +10,11 @@ rebase.backend::
>         'apply' or 'merge'.  In the future, if the merge backend gains
>         all remaining capabilities of the apply backend, this setting
>         may become unused.
> ++
> +If set multiple times, the last value corresponding to a recognized
> +backend is used. This is for forward compatibility, as it allows
> +specifying a rebase backend that Git does not know about yet along
> +with a backend known today as a fallback.
>
>  rebase.stat::
>         Whether to show a diffstat of what changed upstream since the last
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index ffa467aad52..5b0fab9741f 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -56,10 +56,18 @@ enum empty_type {
>         EMPTY_ASK
>  };
>
> +enum rebase_backend {
> +       BACKEND_UNSPECIFIED = 0,
> +       BACKEND_UNRECOGNIZED,
> +       BACKEND_APPLY,
> +       BACKEND_MERGE,
> +};
> +
>  struct rebase_options {
>         enum rebase_type type;
>         enum empty_type empty;
> -       const char *default_backend;
> +       enum rebase_backend configured_backend;
> +       const char *last_specified_backend;
>         const char *state_dir;
>         struct commit *upstream;
>         const char *upstream_name;
> @@ -100,7 +108,6 @@ struct rebase_options {
>  #define REBASE_OPTIONS_INIT {                          \
>                 .type = REBASE_UNSPECIFIED,             \
>                 .empty = EMPTY_UNSPECIFIED,             \
> -               .default_backend = "merge",             \
>                 .flags = REBASE_NO_QUIET,               \
>                 .git_am_opts = ARGV_ARRAY_INIT,         \
>                 .git_format_patch_opt = STRBUF_INIT     \
> @@ -1224,6 +1231,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
>         return status ? -1 : 0;
>  }
>
> +static enum rebase_backend parse_rebase_backend(const char *value)
> +{
> +       if (!strcmp(value, "apply"))
> +               return BACKEND_APPLY;
> +       if (!strcmp(value, "merge"))
> +               return BACKEND_MERGE;
> +       return BACKEND_UNRECOGNIZED;
> +}
> +
>  static int rebase_config(const char *var, const char *value, void *data)
>  {
>         struct rebase_options *opts = data;
> @@ -1264,7 +1280,18 @@ static int rebase_config(const char *var, const char *value, void *data)
>         }
>
>         if (!strcmp(var, "rebase.backend")) {
> -               return git_config_string(&opts->default_backend, var, value);
> +               enum rebase_backend val;
> +               if (!value)
> +                       return config_error_nonbool(var);
> +               val = parse_rebase_backend(value);
> +               if (opts->configured_backend == BACKEND_UNSPECIFIED)
> +                       opts->configured_backend = val;
> +               else if (val == BACKEND_UNRECOGNIZED)
> +                       ; /* Unrecognized rebase backend. Ignore it. */
> +               else
> +                       opts->configured_backend = val;
> +               opts->last_specified_backend = value;
> +               return 0;
>         }
>
>         return git_default_config(var, value, data);
> @@ -1903,14 +1930,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>                 }
>         }
>
> +       if (options.configured_backend == BACKEND_UNRECOGNIZED)
> +               die(_("unknown rebase backend: %s"),
> +                   options.last_specified_backend);
> +
>         if (options.type == REBASE_UNSPECIFIED) {
> -               if (!strcmp(options.default_backend, "merge"))
> +               switch (options.configured_backend) {
> +               case BACKEND_UNSPECIFIED:
> +               case BACKEND_MERGE:
>                         imply_merge(&options, "--merge");
> -               else if (!strcmp(options.default_backend, "apply"))
> +                       break;
> +               case BACKEND_APPLY:
>                         options.type = REBASE_APPLY;
> -               else
> -                       die(_("Unknown rebase backend: %s"),
> -                           options.default_backend);
> +                       break;
> +               default:
> +                       BUG("unexpected backend %d",
> +                           (int) options.configured_backend);
> +               }
>         }
>
>         switch (options.type) {
> diff --git a/t/t3435-rebase-backend.sh b/t/t3435-rebase-backend.sh
> new file mode 100755
> index 00000000000..8b9ba6f1894
> --- /dev/null
> +++ b/t/t3435-rebase-backend.sh
> @@ -0,0 +1,97 @@
> +#!/bin/sh
> +
> +test_description='rebase.backend tests
> +
> +Checks of config parsing for the [rebase] backend setting.  We detect
> +which backend was used by checking which directory was created to hold
> +state.'
> +
> +. ./test-lib.sh
> +
> +# usage: test_backend_choice <expectation> <command>
> +#
> +# Tests that the chosen backend for rebase command <command>
> +# is <expectation> ("merge" or "apply").
> +test_backend_choice () {
> +       expect=$1 &&
> +       shift &&
> +
> +       test_must_fail git "$@" master topic &&
> +       case $expect in
> +       apply)
> +               test_path_is_dir .git/rebase-apply &&
> +               test_path_is_missing .git/rebase-merge
> +               ;;
> +       merge)
> +               test_path_is_dir .git/rebase-merge &&
> +               test_path_is_missing .git/rebase-apply
> +               ;;
> +       *)
> +               error "unrecognized expectation $expect"
> +       esac
> +}
> +
> +test_expect_success 'setup' '
> +       test_commit base &&
> +       test_commit sidea conflict.txt myway &&
> +       git checkout -b topic base &&
> +       test_commit sideb conflict.txt thehighway
> +'
> +
> +test_expect_success '--apply uses apply backend' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice apply rebase --apply
> +'
> +
> +test_expect_success '--merge uses merge backend' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice merge rebase --merge
> +'
> +
> +test_expect_success 'default to merge backend' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice merge rebase
> +'
> +
> +test_expect_success 'config overrides default' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice apply -c rebase.backend=apply rebase
> +'
> +
> +test_expect_success 'option overrides config' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice merge -c rebase.backend=apply rebase --merge
> +'
> +
> +test_expect_success 'last config value wins' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice merge \
> +               -c rebase.backend=apply \
> +               -c rebase.backend=merge \
> +               rebase
> +'
> +
> +test_expect_success 'last config value wins' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice merge \
> +               -c rebase.backend=apply \
> +               -c rebase.backend=merge \
> +               rebase
> +'

Um, copy-and-paste-and-forget-to-edit?  This test is identical to the
one above it and thus is not useful; I think you meant to flip the
order of config options and flip the result (and maybe give a slightly
different test name to it)?

> +
> +test_expect_success 'misspelled backend without fallback is diagnosed' '
> +       test_must_fail \
> +               git -c rebase.backend=appply rebase master topic 2>message &&
> +       test_i18ngrep "unknown rebase backend" message &&
> +       grep appply message
> +'
> +
> +test_expect_success 'forward compatibility by skipping unrecognized values' '
> +       test_when_finished "git rebase --abort" &&
> +       test_backend_choice apply \
> +               -c rebase.backend=apply \
> +               -c rebase.backend=futuristic \
> +               rebase
> +'
> +
> +test_done

Didn't spot anything other than that one test issue in looking over things.

^ permalink raw reply	[flat|nested] 161+ messages in thread

end of thread, other threads:[~2020-03-17  4:45 UTC | newest]

Thread overview: 161+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-20 17:09 [PATCH 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
2019-12-20 21:29   ` Junio C Hamano
2019-12-21  0:32     ` Elijah Newren
2019-12-21 18:52       ` Elijah Newren
2019-12-21 23:49       ` Junio C Hamano
2019-12-20 17:09 ` [PATCH 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
2019-12-20 21:34   ` Junio C Hamano
2019-12-20 17:09 ` [PATCH 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
2019-12-20 21:37   ` Junio C Hamano
2019-12-20 17:09 ` [PATCH 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
2019-12-22  5:11   ` Denton Liu
2019-12-23 17:17     ` Elijah Newren
2019-12-20 17:09 ` [PATCH 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 11/15] contrib: change the prompt for am-based rebases Elijah Newren via GitGitGadget
2019-12-20 23:07   ` SZEDER Gábor
2019-12-21  0:17     ` Elijah Newren
2019-12-20 17:09 ` [PATCH 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
2019-12-20 17:09 ` [PATCH 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
2019-12-20 18:51 ` [PATCH 00/15] rebase: make the default backend configurable Alban Gruin
2019-12-20 18:55   ` Elijah Newren
2019-12-23 18:49 ` [PATCH v2 " Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
2019-12-23 22:00     ` Denton Liu
2019-12-23 18:49   ` [PATCH v2 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
2019-12-23 18:49   ` [PATCH v2 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
2019-12-24 19:54   ` [PATCH v3 00/15] rebase: make the default backend configurable Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 01/15] rebase: extend the options for handling of empty commits Elijah Newren via GitGitGadget
2020-01-07 14:37       ` Phillip Wood
2020-01-07 19:15         ` Elijah Newren
2020-01-08 14:27           ` Phillip Wood
2020-01-09 21:32             ` Johannes Schindelin
2019-12-24 19:54     ` [PATCH v3 02/15] t3406: simplify an already simple test Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 03/15] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 04/15] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 05/15] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 06/15] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 07/15] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 08/15] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 09/15] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 10/15] rebase: add an --am option Elijah Newren via GitGitGadget
2020-01-07 14:43       ` Phillip Wood
2020-01-07 19:26         ` Elijah Newren
2020-01-07 20:11           ` Junio C Hamano
2020-01-08 14:32             ` Phillip Wood
2020-01-08 17:18               ` Junio C Hamano
2020-01-08 18:55                 ` Phillip Wood
2019-12-24 19:54     ` [PATCH v3 11/15] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 12/15] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 13/15] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 14/15] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
2019-12-24 19:54     ` [PATCH v3 15/15] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
2020-01-10 23:14       ` Jonathan Nieder
2020-01-11  1:16         ` Elijah Newren
2020-01-11 14:41           ` Phillip Wood
2020-01-12 17:59             ` Johannes Schindelin
2020-01-16  6:32               ` Elijah Newren
2020-01-16  7:58                 ` Jonathan Nieder
2020-01-16  8:06                   ` Jonathan Nieder
2020-01-16 16:18                     ` Elijah Newren
2020-01-16 20:35                       ` Jonathan Nieder
2020-01-16 21:30                         ` Elijah Newren
2020-01-16 22:39                           ` Jonathan Nieder
2020-01-16 23:19                             ` Elijah Newren
2020-01-16 23:25                           ` Junio C Hamano
2020-01-17  0:51                             ` Elijah Newren
2020-01-16 15:35                   ` Elijah Newren
2020-01-16 20:05                   ` Junio C Hamano
2020-01-16 10:48                 ` Johannes Schindelin
2020-01-12 21:23             ` Junio C Hamano
2020-01-15 19:50             ` Jonathan Nieder
2020-01-15 21:59               ` Emily Shaffer
2020-01-16  6:14     ` [PATCH v4 00/19] rebase: make the default backend configurable Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
2020-02-09 15:59         ` Phillip Wood
2020-02-13 18:35           ` Elijah Newren
2020-01-16  6:14       ` [PATCH v4 02/19] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
2020-02-09 15:59         ` Phillip Wood
2020-02-13 18:52           ` Elijah Newren
2020-01-16  6:14       ` [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
2020-02-10 14:27         ` Phillip Wood
2020-02-13 18:54           ` Elijah Newren
2020-02-16 14:46             ` Phillip Wood
2020-01-16  6:14       ` [PATCH v4 05/19] t3406: simplify an already simple test Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 06/19] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 07/19] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 08/19] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 09/19] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 10/19] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 11/19] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 12/19] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 13/19] rebase: add an --am option Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 14/19] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 15/19] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 16/19] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 17/19] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 18/19] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
2020-01-16  6:14       ` [PATCH v4 19/19] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
2020-01-17 16:58       ` [PATCH v4 00/19] rebase: make the default backend configurable Phillip Wood
2020-02-05 21:06         ` Junio C Hamano
2020-02-05 22:38           ` Elijah Newren
2020-02-15 21:36       ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 01/20] git-rebase.txt: update description of --allow-empty-message Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 02/20] t3404: directly test the behavior of interest Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 03/20] rebase (interactive-backend): make --keep-empty the default Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 04/20] rebase (interactive-backend): fix handling of commits that become empty Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 05/20] t3406: simplify an already simple test Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 06/20] rebase, sequencer: remove the broken GIT_QUIET handling Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 07/20] rebase: make sure to pass along the quiet flag to the sequencer Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 08/20] rebase: fix handling of restrict_revision Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 09/20] t3432: make these tests work with either am or merge backends Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 10/20] rebase: allow more types of rebases to fast-forward Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 11/20] git-rebase.txt: add more details about behavioral differences of backends Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 12/20] rebase: move incompatibility checks between backend options a bit earlier Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 13/20] rebase: add an --am option Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 14/20] git-prompt: change the prompt for interactive-based rebases Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 15/20] rebase: drop '-i' from the reflog " Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 16/20] rebase tests: mark tests specific to the am-backend with --am Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 17/20] rebase tests: repeat some tests using the merge backend instead of am Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 18/20] rebase: make the backend configurable via config setting Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 19/20] rebase: change the default backend from "am" to "merge" Elijah Newren via GitGitGadget
2020-02-15 21:36         ` [PATCH v5 20/20] rebase: rename the two primary rebase backends Elijah Newren via GitGitGadget
2020-03-12 15:13           ` Emily Shaffer
2020-03-12 16:33             ` Elijah Newren
2020-03-12 17:55               ` Jonathan Nieder
2020-03-12 18:39                 ` Elijah Newren
2020-03-12 18:46                   ` Jonathan Nieder
2020-03-12 19:31                     ` Elijah Newren
2020-03-17  2:58                       ` Jonathan Nieder
2020-03-17  4:45                         ` Elijah Newren
2020-03-12 19:54                     ` Junio C Hamano
2020-03-12 19:07               ` Junio C Hamano
2020-03-12 19:12                 ` Jonathan Nieder
2020-03-12 19:12               ` Junio C Hamano
2020-03-12 19:29                 ` Elijah Newren
2020-03-12 20:37                   ` Jeff King
2020-03-12 21:27                     ` Junio C Hamano
2020-03-12 22:06                       ` Elijah Newren
2020-03-13  0:04                         ` Junio C Hamano
2020-03-12 23:30                       ` Jonathan Nieder
2020-02-16 15:01         ` [PATCH v5 00/20] rebase: make the default backend configurable and change the default Phillip Wood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).