about summary refs log tree commit diff
path: root/third_party/git/builtin/rebase.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/git/builtin/rebase.c')
-rw-r--r--third_party/git/builtin/rebase.c414
1 files changed, 149 insertions, 265 deletions
diff --git a/third_party/git/builtin/rebase.c b/third_party/git/builtin/rebase.c
index bff53d5d167e..670096c065f5 100644
--- a/third_party/git/builtin/rebase.c
+++ b/third_party/git/builtin/rebase.c
@@ -29,8 +29,8 @@
 #include "rebase-interactive.h"
 
 static char const * const builtin_rebase_usage[] = {
-	N_("git rebase [-i] [options] [--exec <cmd>] "
-		"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
+	N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
+		"[<upstream>] [<branch>]"),
 	N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
 		"--root [<branch>]"),
 	N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@ -44,22 +44,14 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
 
 enum rebase_type {
 	REBASE_UNSPECIFIED = -1,
-	REBASE_APPLY,
+	REBASE_AM,
 	REBASE_MERGE,
+	REBASE_INTERACTIVE,
 	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 *default_backend;
 	const char *state_dir;
 	struct commit *upstream;
 	const char *upstream_name;
@@ -70,7 +62,7 @@ struct rebase_options {
 	const char *onto_name;
 	const char *revisions;
 	const char *switch_to;
-	int root, root_with_onto;
+	int root;
 	struct object_id *squash_onto;
 	struct commit *restrict_revision;
 	int dont_finish_rebase;
@@ -85,6 +77,7 @@ struct rebase_options {
 	const char *action;
 	int signoff;
 	int allow_rerere_autoupdate;
+	int keep_empty;
 	int autosquash;
 	char *gpg_sign_opt;
 	int autostash;
@@ -99,8 +92,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	\
@@ -119,9 +110,6 @@ 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.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);
@@ -129,11 +117,6 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	if (opts->strategy_opts)
 		parse_strategy_opts(&replay, opts->strategy_opts);
 
-	if (opts->squash_onto) {
-		oidcpy(&replay.squash_onto, opts->squash_onto);
-		replay.have_squash_onto = 1;
-	}
-
 	return replay;
 }
 
@@ -258,17 +241,21 @@ static int edit_todo_file(unsigned flags)
 }
 
 static int get_revision_ranges(struct commit *upstream, struct commit *onto,
-			       struct object_id *orig_head, const char **head_hash,
+			       const char **head_hash,
 			       char **revisions, char **shortrevisions)
 {
 	struct commit *base_rev = upstream ? upstream : onto;
 	const char *shorthead;
+	struct object_id orig_head;
 
-	*head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ);
+	if (get_oid("HEAD", &orig_head))
+		return error(_("no HEAD?"));
+
+	*head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
 	*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
 						   *head_hash);
 
-	shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
+	shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
 
 	if (upstream) {
 		const char *shortrev;
@@ -322,8 +309,12 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 	struct replay_opts replay = get_replay_opts(opts);
 	struct string_list commands = STRING_LIST_INIT_DUP;
 
-	if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head,
-				&head_hash, &revisions, &shortrevisions))
+	if (prepare_branch_to_be_rebased(the_repository, &replay,
+					 opts->switch_to))
+		return -1;
+
+	if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
+				&revisions, &shortrevisions))
 		return -1;
 
 	if (init_basic_state(&replay,
@@ -341,8 +332,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_pushf(&make_script_args, "^%s",
-				 oid_to_hex(&opts->restrict_revision->object.oid));
+		argv_array_push(&make_script_args,
+				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,
@@ -371,7 +362,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
 	return ret;
 }
 
-static int run_sequencer_rebase(struct rebase_options *opts,
+static int run_rebase_interactive(struct rebase_options *opts,
 				  enum action command)
 {
 	unsigned flags = 0;
@@ -379,10 +370,10 @@ static int run_sequencer_rebase(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;
-	flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
 	flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 
 	switch (command) {
@@ -442,21 +433,6 @@ static int run_sequencer_rebase(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);
-
-	/*
-	 * If we ever want to remap --keep-empty to --empty=keep, insert:
-	 * 	opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
-	 */
-	opts->type = REBASE_MERGE;
-	return 0;
-}
-
 static const char * const builtin_rebase_interactive_usage[] = {
 	N_("git rebase--interactive [<options>]"),
 	NULL
@@ -470,13 +446,9 @@ 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),
-		{ 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),
+		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(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")),
@@ -546,26 +518,28 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 		warning(_("--[no-]rebase-cousins has no effect without "
 			  "--rebase-merges"));
 
-	return !!run_sequencer_rebase(&opts, command);
+	return !!run_rebase_interactive(&opts, command);
 }
 
-static int is_merge(struct rebase_options *opts)
+static int is_interactive(struct rebase_options *opts)
 {
-	return opts->type == REBASE_MERGE ||
+	return opts->type == REBASE_INTERACTIVE ||
 		opts->type == REBASE_PRESERVE_MERGES;
 }
 
-static void imply_merge(struct rebase_options *opts, const char *option)
+static void imply_interactive(struct rebase_options *opts, const char *option)
 {
 	switch (opts->type) {
-	case REBASE_APPLY:
+	case REBASE_AM:
 		die(_("%s requires an interactive rebase"), option);
 		break;
-	case REBASE_MERGE:
+	case REBASE_INTERACTIVE:
 	case REBASE_PRESERVE_MERGES:
 		break;
+	case REBASE_MERGE:
+		/* we now implement --merge via --interactive */
 	default:
-		opts->type = REBASE_MERGE; /* implied */
+		opts->type = REBASE_INTERACTIVE; /* implied */
 		break;
 	}
 }
@@ -691,8 +665,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));
-	if (!(opts->flags & REBASE_NO_QUIET))
-		write_file(state_dir_path("quiet", opts), "%s", "");
+	write_file(state_dir_path("quiet", opts), "%s",
+		   opts->flags & REBASE_NO_QUIET ? "" : "t");
 	if (opts->flags & REBASE_VERBOSE)
 		write_file(state_dir_path("verbose", opts), "%s", "");
 	if (opts->strategy)
@@ -710,7 +684,7 @@ static int rebase_write_basic_state(struct rebase_options *opts)
 		write_file(state_dir_path("gpg_sign_opt", opts), "%s",
 			   opts->gpg_sign_opt);
 	if (opts->signoff)
-		write_file(state_dir_path("signoff", opts), "--signoff");
+		write_file(state_dir_path("strategy", opts), "--signoff");
 
 	return 0;
 }
@@ -774,7 +748,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_MERGE) {
+	if (opts->type == REBASE_INTERACTIVE) {
 		struct replay_opts replay = REPLAY_OPTS_INIT;
 
 		replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1037,8 +1011,7 @@ static int run_am(struct rebase_options *opts)
 	argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
 			 "--full-index", "--cherry-pick", "--right-only",
 			 "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
-			 "--no-cover-letter", "--pretty=mboxrd", "--topo-order",
-			 "--no-base", NULL);
+			 "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL);
 	if (opts->git_format_patch_opt.len)
 		argv_array_split(&format_patch.args,
 				 opts->git_format_patch_opt.buf);
@@ -1107,8 +1080,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 	int status;
 	const char *backend, *backend_func;
 
-	if (opts->type == REBASE_MERGE) {
-		/* Run sequencer-based rebase */
+	if (opts->type == REBASE_INTERACTIVE) {
+		/* Run builtin interactive rebase */
 		setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
 		if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
 			setenv("GIT_SEQUENCE_EDITOR", ":", 1);
@@ -1121,11 +1094,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 			opts->gpg_sign_opt = tmp;
 		}
 
-		status = run_sequencer_rebase(opts, action);
+		status = run_rebase_interactive(opts, action);
 		goto finished_rebase;
 	}
 
-	if (opts->type == REBASE_APPLY) {
+	if (opts->type == REBASE_AM) {
 		status = run_am(opts);
 		goto finished_rebase;
 	}
@@ -1145,6 +1118,8 @@ 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);
@@ -1162,6 +1137,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, "autosquash", opts->autosquash ? "t" : "");
 	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
 	add_var(&script_snippet, "cmd", opts->cmd);
@@ -1179,7 +1155,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_merge(opts) &&
+	if (is_interactive(opts) &&
 	    !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
 		strbuf_addstr(&script_snippet,
 			      "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
@@ -1204,8 +1180,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_MERGE)
-		; /* merge backend cleans up after itself */
+	else if (opts->type == REBASE_INTERACTIVE)
+		; /* interactive rebase cleans up after itself */
 	else if (status == 0) {
 		if (!file_exists(state_dir_path("stopped-sha", opts)))
 			finish_rebase(opts);
@@ -1263,10 +1239,6 @@ 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);
 }
 
@@ -1288,60 +1260,28 @@ static int is_linear_history(struct commit *from, struct commit *to)
 	return 1;
 }
 
-static int can_fast_forward(struct commit *onto, struct commit *upstream,
-			    struct commit *restrict_revision,
-			    struct object_id *head_oid, struct object_id *merge_base)
+static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
+			    struct object_id *merge_base)
 {
 	struct commit *head = lookup_commit(the_repository, head_oid);
-	struct commit_list *merge_bases = NULL;
-	int res = 0;
+	struct commit_list *merge_bases;
+	int res;
 
 	if (!head)
-		goto done;
+		return 0;
 
 	merge_bases = get_merge_bases(onto, head);
-	if (!merge_bases || merge_bases->next) {
+	if (merge_bases && !merge_bases->next) {
+		oidcpy(merge_base, &merge_bases->item->object.oid);
+		res = oideq(merge_base, &onto->object.oid);
+	} else {
 		oidcpy(merge_base, &null_oid);
-		goto done;
+		res = 0;
 	}
-
-	oidcpy(merge_base, &merge_bases->item->object.oid);
-	if (!oideq(merge_base, &onto->object.oid))
-		goto done;
-
-	if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))
-		goto done;
-
-	if (!upstream)
-		goto done;
-
-	free_commit_list(merge_bases);
-	merge_bases = get_merge_bases(upstream, head);
-	if (!merge_bases || merge_bases->next)
-		goto done;
-
-	if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
-		goto done;
-
-	res = 1;
-
-done:
 	free_commit_list(merge_bases);
 	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_APPLY;
-
-	return 0;
-}
-
 /* -i followed by -m is still -i */
 static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
 {
@@ -1350,7 +1290,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_merge(opts))
+	if (!is_interactive(opts))
 		opts->type = REBASE_MERGE;
 
 	return 0;
@@ -1365,35 +1305,12 @@ 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_MERGE;
+	opts->type = REBASE_INTERACTIVE;
 	opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
 
 	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);
@@ -1429,14 +1346,14 @@ static void set_reflog_action(struct rebase_options *options)
 	const char *env;
 	struct strbuf buf = STRBUF_INIT;
 
-	if (!is_merge(options))
+	if (!is_interactive(options))
 		return;
 
 	env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
 	if (env && strcmp("rebase", env))
 		return; /* only override it if it is "rebase" */
 
-	strbuf_addf(&buf, "rebase (%s)", options->action);
+	strbuf_addf(&buf, "rebase -i (%s)", options->action);
 	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
 	strbuf_release(&buf);
 }
@@ -1459,7 +1376,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	struct rebase_options options = REBASE_OPTIONS_INIT;
 	const char *branch_name;
 	int ret, flags, total_argc, in_progress = 0;
-	int keep_base = 0;
 	int ok_to_skip_pre_rebase = 0;
 	struct strbuf msg = STRBUF_INIT;
 	struct strbuf revisions = STRBUF_INIT;
@@ -1474,18 +1390,15 @@ 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"),
 			   N_("rebase onto given branch instead of upstream")),
-		OPT_BOOL(0, "keep-base", &keep_base,
-			 N_("use the merge-base of upstream and branch as the current base")),
 		OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
 			 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),
@@ -1526,10 +1439,6 @@ 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, "apply", &options, NULL,
-			N_("use apply 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,
@@ -1538,18 +1447,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			N_("let the user edit the list of commits to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
 			parse_opt_interactive },
-		OPT_SET_INT_F('p', "preserve-merges", &options.type,
-			      N_("(DEPRECATED) try to recreate merges instead of "
-				 "ignoring them"),
-			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
+		OPT_SET_INT('p', "preserve-merges", &options.type,
+			    N_("(DEPRECATED) try to recreate merges instead of "
+			       "ignoring them"), REBASE_PRESERVE_MERGES),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
-		OPT_CALLBACK_F(0, "empty", &options, "{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,
-			parse_opt_keep_empty },
+		OPT_BOOL('k', "keep-empty", &options.keep_empty,
+			 N_("preserve empty commits during rebase")),
 		OPT_BOOL(0, "autosquash", &options.autosquash,
 			 N_("move commits that begin with "
 			    "squash!/fixup! under -i")),
@@ -1561,10 +1464,9 @@ 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_F(0, "allow-empty-message",
-			   &options.allow_empty_message,
-			   N_("allow rebasing commits with empty messages"),
-			   PARSE_OPT_HIDDEN),
+		OPT_BOOL(0, "allow-empty-message",
+			 &options.allow_empty_message,
+			 N_("allow rebasing commits with empty messages")),
 		{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
 			N_("mode"),
 			N_("try to rebase merges instead of skipping them"),
@@ -1604,7 +1506,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_APPLY;
+		options.type = REBASE_AM;
 		options.state_dir = apply_dir();
 	} else if (is_directory(merge_dir())) {
 		strbuf_reset(&buf);
@@ -1616,7 +1518,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_MERGE;
+				options.type = REBASE_INTERACTIVE;
 				options.flags |= REBASE_INTERACTIVE_EXPLICIT;
 			} else
 				options.type = REBASE_MERGE;
@@ -1645,23 +1547,16 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		warning(_("git rebase --preserve-merges is deprecated. "
 			  "Use --rebase-merges instead."));
 
-	if (keep_base) {
-		if (options.onto_name)
-			die(_("cannot combine '--keep-base' with '--onto'"));
-		if (options.root)
-			die(_("cannot combine '--keep-base' with '--root'"));
-	}
-
 	if (action != ACTION_NONE && !in_progress)
 		die(_("No rebase in progress?"));
 	setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
 
-	if (action == ACTION_EDIT_TODO && !is_merge(&options))
+	if (action == ACTION_EDIT_TODO && !is_interactive(&options))
 		die(_("The --edit-todo action can only be used during "
 		      "interactive rebase."));
 
 	if (trace2_is_enabled()) {
-		if (is_merge(&options))
+		if (is_interactive(&options))
 			trace2_cmd_mode("interactive");
 		else if (exec.nr)
 			trace2_cmd_mode("interactive-exec");
@@ -1737,7 +1632,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		goto cleanup;
 	}
 	case ACTION_QUIT: {
-		if (options.type == REBASE_MERGE) {
+		if (options.type == REBASE_INTERACTIVE) {
 			struct replay_opts replay = REPLAY_OPTS_INIT;
 
 			replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1786,20 +1681,13 @@ 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"))
-			allow_preemptive_ff = 0;
+			options.flags |= REBASE_FORCE;
 		else if (skip_prefix(option, "-C", &p)) {
 			while (*p)
 				if (!isdigit(*(p++)))
@@ -1819,8 +1707,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.empty != EMPTY_UNSPECIFIED)
-		imply_merge(&options, "--empty");
+	if (options.keep_empty)
+		imply_interactive(&options, "--keep-empty");
 
 	if (gpg_sign) {
 		free(options.gpg_sign_opt);
@@ -1830,7 +1718,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (exec.nr) {
 		int i;
 
-		imply_merge(&options, "--exec");
+		imply_interactive(&options, "--exec");
 
 		strbuf_reset(&buf);
 		for (i = 0; i < exec.nr; i++)
@@ -1846,7 +1734,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_merge(&options, "--rebase-merges");
+		imply_interactive(&options, "--rebase-merges");
 	}
 
 	if (strategy_options.nr) {
@@ -1865,9 +1753,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	if (options.strategy) {
 		options.strategy = xstrdup(options.strategy);
 		switch (options.type) {
-		case REBASE_APPLY:
+		case REBASE_AM:
 			die(_("--strategy requires --merge or --interactive"));
 		case REBASE_MERGE:
+		case REBASE_INTERACTIVE:
 		case REBASE_PRESERVE_MERGES:
 			/* compatible */
 			break;
@@ -1880,65 +1769,47 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	if (options.type == REBASE_MERGE)
-		imply_merge(&options, "--merge");
+		imply_interactive(&options, "--merge");
 
 	if (options.root && !options.onto_name)
-		imply_merge(&options, "--root without --onto");
+		imply_interactive(&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_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_merge(&options))
-				die(_("cannot combine apply options with "
-				      "merge options"));
-			else
-				options.type = REBASE_APPLY;
-		}
-	}
-
-	if (options.type == REBASE_UNSPECIFIED) {
-		if (!strcmp(options.default_backend, "merge"))
-			imply_merge(&options, "--merge");
-		else if (!strcmp(options.default_backend, "apply"))
-			options.type = REBASE_APPLY;
-		else
-			die(_("Unknown rebase backend: %s"),
-			    options.default_backend);
-	}
-
 	switch (options.type) {
 	case REBASE_MERGE:
+	case REBASE_INTERACTIVE:
 	case REBASE_PRESERVE_MERGES:
 		options.state_dir = merge_dir();
 		break;
-	case REBASE_APPLY:
+	case REBASE_AM:
 		options.state_dir = apply_dir();
 		break;
 	default:
-		BUG("options.type was just set above; should be unreachable.");
+		/* the default rebase backend is `--am` */
+		options.type = REBASE_AM;
+		options.state_dir = apply_dir();
+		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_merge(&options))
+	if (reschedule_failed_exec > 0 && !is_interactive(&options))
 		die(_("--reschedule-failed-exec requires "
 		      "--exec or --interactive"));
 	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 "
@@ -1962,6 +1833,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			      "'--reschedule-failed-exec'"));
 	}
 
+	if (options.rebase_merges) {
+		if (strategy_options.nr)
+			die(_("cannot combine '--rebase-merges' with "
+			      "'--strategy-option'"));
+		if (options.strategy)
+			die(_("cannot combine '--rebase-merges' with "
+			      "'--strategy'"));
+	}
+
 	if (!options.root) {
 		if (argc < 1) {
 			struct branch *branch;
@@ -1992,9 +1872,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			options.squash_onto = &squash_onto;
 			options.onto_name = squash_onto_name =
 				xstrdup(oid_to_hex(&squash_onto));
-		} else
-			options.root_with_onto = 1;
-
+		}
 		options.upstream_name = NULL;
 		options.upstream = NULL;
 		if (argc > 1)
@@ -2004,22 +1882,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	/* Make sure the branch to rebase onto is valid. */
-	if (keep_base) {
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, options.upstream_name);
-		strbuf_addstr(&buf, "...");
-		options.onto_name = xstrdup(buf.buf);
-	} else if (!options.onto_name)
+	if (!options.onto_name)
 		options.onto_name = options.upstream_name;
 	if (strstr(options.onto_name, "...")) {
-		if (get_oid_mb(options.onto_name, &merge_base) < 0) {
-			if (keep_base)
-				die(_("'%s': need exactly one merge base with branch"),
-				    options.upstream_name);
-			else
-				die(_("'%s': need exactly one merge base"),
-				    options.onto_name);
-		}
+		if (get_oid_mb(options.onto_name, &merge_base) < 0)
+			die(_("'%s': need exactly one merge base"),
+			    options.onto_name);
 		options.onto = lookup_commit_or_die(&merge_base,
 						    options.onto_name);
 	} else {
@@ -2044,11 +1912,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		/* Is it a local branch? */
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "refs/heads/%s", branch_name);
-		if (!read_ref(buf.buf, &options.orig_head)) {
-			die_if_checked_out(buf.buf, 1);
+		if (!read_ref(buf.buf, &options.orig_head))
 			options.head_name = xstrdup(buf.buf);
 		/* If not is it a valid ref (branch or commit)? */
-		} else if (!get_oid(branch_name, &options.orig_head))
+		else if (!get_oid(branch_name, &options.orig_head))
 			options.head_name = NULL;
 		else
 			die(_("fatal: no such branch/commit '%s'"),
@@ -2101,6 +1968,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				state_dir_path("autostash", &options);
 			struct child_process stash = CHILD_PROCESS_INIT;
 			struct object_id oid;
+			struct commit *head =
+				lookup_commit_reference(the_repository,
+							&options.orig_head);
 
 			argv_array_pushl(&stash.args,
 					 "stash", "create", "autostash", NULL);
@@ -2121,9 +1991,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 				    options.state_dir);
 			write_file(autostash, "%s", oid_to_hex(&oid));
 			printf(_("Created autostash: %s\n"), buf.buf);
-			if (reset_head(NULL, "reset --hard",
+			if (reset_head(&head->object.oid, "reset --hard",
 				       NULL, RESET_HEAD_HARD, NULL, NULL) < 0)
 				die(_("could not reset --hard"));
+			printf(_("HEAD is now at %s"),
+			       find_unique_abbrev(&head->object.oid,
+						  DEFAULT_ABBREV));
+			strbuf_reset(&buf);
+			pp_commit_easy(CMIT_FMT_ONELINE, head, &buf);
+			if (buf.len > 0)
+				printf(" %s", buf.buf);
+			putchar('\n');
 
 			if (discard_index(the_repository->index) < 0 ||
 				repo_read_index(the_repository) < 0)
@@ -2144,25 +2022,31 @@ 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.
-	 *
-	 * Note that can_fast_forward() initializes merge_base, so we have to
-	 * call it before checking allow_preemptive_ff.
+	 * but this should be done only when upstream and onto are the same
+	 * and if this is not an interactive rebase.
 	 */
-	if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
-		    &options.orig_head, &merge_base) &&
-	    allow_preemptive_ff) {
+	if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
+	    !is_interactive(&options) && !options.restrict_revision &&
+	    options.upstream &&
+	    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
 		int flag;
 
 		if (!(options.flags & REBASE_FORCE)) {
 			/* Lazily switch to the target branch if needed... */
 			if (options.switch_to) {
+				struct object_id oid;
+
+				if (get_oid(options.switch_to, &oid) < 0) {
+					ret = !!error(_("could not parse '%s'"),
+						      options.switch_to);
+					goto cleanup;
+				}
+
 				strbuf_reset(&buf);
 				strbuf_addf(&buf, "%s: checkout %s",
 					    getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
 					    options.switch_to);
-				if (reset_head(&options.orig_head, "checkout",
+				if (reset_head(&oid, "checkout",
 					       options.head_name,
 					       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
 					       NULL, buf.buf) < 0) {
@@ -2227,7 +2111,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		diff_flush(&opts);
 	}
 
-	if (is_merge(&options))
+	if (is_interactive(&options))
 		goto run_rebase;
 
 	/* Detach HEAD and reset the tree */