Merge branch 'en/rebase-backend'

"git rebase" has learned to use the merge backend (i.e. the
machinery that drives "rebase -i") by default, while allowing
"--apply" option to use the "apply" backend (e.g. the moral
equivalent of "format-patch piped to am").  The rebase.backend
configuration variable can be set to customize.

* en/rebase-backend:
  rebase: rename the two primary rebase backends
  rebase: change the default backend from "am" to "merge"
  rebase: make the backend configurable via config setting
  rebase tests: repeat some tests using the merge backend instead of am
  rebase tests: mark tests specific to the am-backend with --am
  rebase: drop '-i' from the reflog for interactive-based rebases
  git-prompt: change the prompt for interactive-based rebases
  rebase: add an --am option
  rebase: move incompatibility checks between backend options a bit earlier
  git-rebase.txt: add more details about behavioral differences of backends
  rebase: allow more types of rebases to fast-forward
  t3432: make these tests work with either am or merge backends
  rebase: fix handling of restrict_revision
  rebase: make sure to pass along the quiet flag to the sequencer
  rebase, sequencer: remove the broken GIT_QUIET handling
  t3406: simplify an already simple test
  rebase (interactive-backend): fix handling of commits that become empty
  rebase (interactive-backend): make --keep-empty the default
  t3404: directly test the behavior of interest
  git-rebase.txt: update description of --allow-empty-message
This commit is contained in:
Junio C Hamano 2020-03-02 15:07:18 -08:00
commit 8c22bd9ff9
25 changed files with 702 additions and 286 deletions

View File

@ -5,6 +5,12 @@ 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
'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
rebase. False by default.

View File

@ -258,16 +258,45 @@ See also INCOMPATIBLE OPTIONS below.
original branch. The index and working tree are also left
unchanged as a result.
--keep-empty::
Keep the commits that do not change anything from its
parents in the result.
--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.
--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
override that behavior, allowing commits with empty changes to
be rebased. Now commits with no changes do not cause rebasing
to halt.
+
See also BEHAVIORAL DIFFERENCES and 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.
@ -286,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
@ -356,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 --apply.
+
See also INCOMPATIBLE OPTIONS below.
@ -394,8 +423,9 @@ 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.
Implies --apply.
+
See also INCOMPATIBLE OPTIONS below.
@ -539,10 +569,11 @@ INCOMPATIBLE OPTIONS
The following options:
* --apply
* --committer-date-is-author-date
* --ignore-date
* --whitespace
* --ignore-whitespace
* --whitespace
* -C
are incompatible with the following options:
@ -557,6 +588,7 @@ are incompatible with the following options:
* --interactive
* --exec
* --keep-empty
* --empty=
* --edit-todo
* --root when used in combination with --onto
@ -565,33 +597,127 @@ 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
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 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 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 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 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
~~~~~~~~~~~~~~~~~~~~~~~~~~
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 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 backend to provide you
warnings in such cases.
Context
~~~~~~~
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
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 merge 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 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 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 merge backend works with the full commits on both sides of history
and thus has no such limitations.
Hooks
~~~~~
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 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.)
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 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/
include::merge-strategies.txt[]

View File

@ -44,14 +44,22 @@ 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
};
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;
@ -77,7 +85,6 @@ struct rebase_options {
const char *action;
int signoff;
int allow_rerere_autoupdate;
int keep_empty;
int autosquash;
char *gpg_sign_opt;
int autostash;
@ -92,6 +99,8 @@ 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 \
@ -110,6 +119,9 @@ 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);
@ -329,8 +341,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,
@ -359,7 +371,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;
@ -367,7 +379,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;
@ -431,6 +442,21 @@ 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);
/*
* 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
@ -444,9 +470,13 @@ 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")),
OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
N_("allow commits with empty messages")),
{ 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, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
N_("keep original branch points of cousins")),
@ -516,28 +546,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;
}
}
@ -663,8 +691,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)
@ -746,7 +774,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;
@ -1079,8 +1107,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);
@ -1093,11 +1121,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;
}
@ -1117,8 +1145,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);
@ -1136,7 +1162,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);
@ -1154,7 +1179,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; ");
@ -1179,8 +1204,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);
@ -1238,6 +1263,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);
}
@ -1301,6 +1330,18 @@ done:
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)
{
@ -1309,7 +1350,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;
@ -1324,12 +1365,35 @@ 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;
}
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);
@ -1365,14 +1429,14 @@ 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);
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);
}
@ -1410,6 +1474,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"),
@ -1420,7 +1485,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),
@ -1461,6 +1526,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, "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,
@ -1474,8 +1543,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 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(0, "autosquash", &options.autosquash,
N_("move commits that begin with "
"squash!/fixup! under -i")),
@ -1487,9 +1561,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"),
@ -1529,7 +1604,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);
@ -1541,7 +1616,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;
@ -1581,12 +1656,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");
@ -1662,7 +1737,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;
@ -1711,13 +1786,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++)))
@ -1737,8 +1819,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_merge(&options, "--empty");
if (gpg_sign) {
free(options.gpg_sign_opt);
@ -1748,7 +1830,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++)
@ -1764,7 +1846,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) {
@ -1783,10 +1865,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;
@ -1799,47 +1880,65 @@ 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");
switch (options.type) {
case REBASE_MERGE:
case REBASE_INTERACTIVE:
case REBASE_PRESERVE_MERGES:
options.state_dir = merge_dir();
break;
case REBASE_AM:
options.state_dir = apply_dir();
break;
default:
/* the default rebase backend is `--am` */
options.type = REBASE_AM;
options.state_dir = apply_dir();
break;
}
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 */
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 (is_interactive(&options) && i >= 0)
die(_("cannot combine am options with either "
"interactive or merge options"));
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_PRESERVE_MERGES:
options.state_dir = merge_dir();
break;
case REBASE_APPLY:
options.state_dir = apply_dir();
break;
default:
BUG("options.type was just set above; should be unreachable.");
}
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))
die(_("--reschedule-failed-exec requires "
"--exec or --interactive"));
if (reschedule_failed_exec >= 0)
options.reschedule_failed_exec = reschedule_failed_exec;
if (options.signoff) {
if (options.type == REBASE_PRESERVE_MERGES)
die("cannot combine '--signoff' with "
@ -2045,12 +2144,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)) {
@ -2133,7 +2234,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 */

View File

@ -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

View File

@ -35,7 +35,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)
{
@ -87,11 +87,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,

View File

@ -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,

View File

@ -160,6 +160,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)
{
@ -290,7 +292,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 +326,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 +630,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)));
@ -1485,23 +1487,30 @@ 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,
struct commit *commit)
{
int index_unchanged, empty_commit;
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 */
@ -1515,13 +1524,15 @@ 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)
return 0;
else
originally_empty = is_original_commit_empty(commit);
if (originally_empty < 0)
return originally_empty;
if (originally_empty)
return 1;
else if (opts->drop_redundant_commits)
return 2;
else
return 0;
}
static struct {
@ -1732,7 +1743,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) {
/*
@ -1937,13 +1948,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
@ -2498,6 +2516,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);
@ -2549,8 +2573,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)
@ -2559,8 +2581,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)
@ -2577,6 +2599,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", "");
@ -3176,7 +3202,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) {
@ -4563,7 +4589,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;
@ -4626,8 +4651,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);
@ -4794,7 +4817,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;
@ -4830,12 +4852,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);
@ -4972,7 +4992,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);

View File

@ -40,6 +40,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;
@ -133,7 +134,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)

View File

@ -165,19 +165,37 @@ 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 &&
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 &&
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 (--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 --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 --apply &&
git rev-parse --verify default-base >expect &&
git rev-parse default~1 >actual &&
test_cmp expect actual
@ -206,9 +224,15 @@ 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 --apply -q is quiet' '
git checkout -b quiet topic &&
git rebase -q master >output.out 2>&1 &&
git rebase --apply -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
'
@ -291,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 &&
@ -301,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 -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 &&

View File

@ -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 A &&
git -c merge.directoryRenames=true rebase --apply A &&
git ls-files -s >out &&
test_line_count = 5 out &&

View File

@ -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
'
@ -222,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
@ -1137,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 --apply --onto HEAD~2 HEAD~ &&
test_must_fail git rebase --edit-todo
) &&
git rebase --abort
@ -1161,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 &&

View File

@ -18,32 +18,29 @@ 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' '
git rebase 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 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 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 topic >out &&
git rebase --apply topic >out &&
test_i18ngrep "Fast-forwarded HEAD to topic" out
'
@ -92,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 --apply reflog-onto &&
git log -g --format=%gs -3 >actual &&
cat >expect <<-\EOF &&
rebase finished: returning to refs/heads/reflog-topic
@ -102,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 --apply reflog-onto &&
git log -g --format=%gs -3 >actual &&
cat >expect <<-\EOF &&
rebase finished: returning to refs/heads/reflog-prefix

View File

@ -96,14 +96,14 @@ testrebase() {
'
}
testrebase "" .git/rebase-apply
testrebase " --apply" .git/rebase-apply
testrebase " --merge" .git/rebase-merge
test_expect_success 'rebase --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 master &&
test_must_fail git rebase --apply master &&
test_path_is_dir .git/rebase-apply &&
head_before=$(git rev-parse HEAD) &&
git rebase --quit &&

View File

@ -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 "" .git/rebase-apply
testrebase " --apply" .git/rebase-apply
testrebase " --merge" .git/rebase-merge
testrebase " --interactive" .git/rebase-merge

View File

@ -26,7 +26,7 @@ test_run_rebase () {
test_linear_range 'd e' c..
"
}
test_run_rebase success ''
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 ''
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 ''
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 ''
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 ''
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 ''
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 ''
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 ''
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 ''
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
@ -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 --apply
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
@ -227,10 +227,10 @@ test_run_rebase () {
test_linear_range 'd k l' c..
"
}
test_run_rebase success ''
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
test_have_prereq !REBASE_P || test_run_rebase success -p
test_run_rebase () {
result=$1
@ -242,10 +242,10 @@ test_run_rebase () {
test_linear_range 'd k l' j..
"
}
test_run_rebase success ''
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
test_have_prereq !REBASE_P || test_run_rebase success -p
test_run_rebase success --rebase-merges
# m
@ -282,7 +282,7 @@ test_run_rebase () {
test_linear_range 'x y' c..
"
}
test_run_rebase success ''
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 ''
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 ''
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 ''
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 ''
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

126
t/t3424-rebase-empty.sh Executable file
View File

@ -0,0 +1,126 @@
#!/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 (apply-backend)' '
test_when_finished "git rebase --abort" &&
git checkout -B testing localmods &&
# 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 &&
test_cmp expect actual
'
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 &&
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 --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 &&
git rebase --skip &&
test_write_lines D C B A >expect &&
git log --format=%s >actual &&
test_cmp expect actual
'
test_done

View File

@ -54,7 +54,7 @@ test_run_rebase () {
test_linear_range 'n o' e..
"
}
test_run_rebase success ''
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' ''
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' ''
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' ''
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

View File

@ -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 --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 &&
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' '
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 &&
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"

View File

@ -28,8 +28,10 @@ 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 " --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" "$*"
}
test_rebase_same_head_ () {
@ -44,19 +46,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 +69,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 +89,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 +110,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

View File

@ -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 --apply' '
git reset --hard D &&
clear_hook_input &&
test_must_fail git rebase --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' '
verify_hook_input
'
test_expect_success 'git rebase --skip' '
test_expect_success 'git rebase --apply --skip' '
git reset --hard D &&
clear_hook_input &&
test_must_fail git rebase --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 --skip' '
verify_hook_input
'
test_expect_success 'git rebase --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 --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 &&

View File

@ -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=apply pull --rebase . ff &&
test_cmp_rev HEAD ff &&
# The above only validates the result. Did we actually bypass rebase?
@ -327,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' '
@ -341,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' '
@ -761,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
)
'

View File

@ -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 --apply describes fake ancestor base' '
(
cd rebase &&
git rebase --abort &&
test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master &&
grep "||||||| constructed merge base" file
)
'

View File

@ -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 --apply 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 --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 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 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 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 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 --apply HEAD^ --onto HEAD^^ &&
cat >expected <<EOF &&
rebase in progress; onto $ONTO
You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.

View File

@ -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' "

View File

@ -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 --apply b1 b2 &&
test_when_finished "git rebase --abort" &&
__git_ps1 >"$actual" &&
test_cmp expected "$actual"