Merge branch 'en/rebase-no-keep-empty'
"git rebase" (again) learns to honor "--no-keep-empty", which lets the user to discard commits that are empty from the beginning (as opposed to the ones that become empty because of rebasing). The interactive rebase also marks commits that are empty in the todo. * en/rebase-no-keep-empty: rebase: fix an incompatible-options error message rebase: reinstate --no-keep-empty rebase -i: mark commits that begin empty in todo editor
This commit is contained in:
@ -277,20 +277,32 @@ See also INCOMPATIBLE OPTIONS below.
|
|||||||
Other options, like --exec, will use the default of drop unless
|
Other options, like --exec, will use the default of drop unless
|
||||||
-i/--interactive is explicitly specified.
|
-i/--interactive is explicitly specified.
|
||||||
+
|
+
|
||||||
Note that commits which start empty are kept, and commits which are
|
Note that commits which start empty are kept (unless --no-keep-empty
|
||||||
clean cherry-picks (as determined by `git log --cherry-mark ...`) are
|
is specified), and commits which are clean cherry-picks (as determined
|
||||||
always dropped.
|
by `git log --cherry-mark ...`) are always dropped.
|
||||||
+
|
+
|
||||||
See also INCOMPATIBLE OPTIONS below.
|
See also INCOMPATIBLE OPTIONS below.
|
||||||
|
|
||||||
|
--no-keep-empty::
|
||||||
--keep-empty::
|
--keep-empty::
|
||||||
No-op. Rebasing commits that started empty (had no change
|
Do not keep commits that start empty before the rebase
|
||||||
relative to their parent) used to fail and this option would
|
(i.e. that do not change anything from its parent) in the
|
||||||
override that behavior, allowing commits with empty changes to
|
result. The default is to keep commits which start empty,
|
||||||
be rebased. Now commits with no changes do not cause rebasing
|
since creating such commits requires passing the --allow-empty
|
||||||
to halt.
|
override flag to `git commit`, signifying that a user is very
|
||||||
|
intentionally creating such a commit and thus wants to keep
|
||||||
|
it.
|
||||||
+
|
+
|
||||||
See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
|
Usage of this flag will probably be rare, since you can get rid of
|
||||||
|
commits that start empty by just firing up an interactive rebase and
|
||||||
|
removing the lines corresponding to the commits you don't want. This
|
||||||
|
flag exists as a convenient shortcut, such as for cases where external
|
||||||
|
tools generate many empty commits and you want them all removed.
|
||||||
|
+
|
||||||
|
For commits which do not start empty but become empty after rebasing,
|
||||||
|
see the --empty flag.
|
||||||
|
+
|
||||||
|
See also INCOMPATIBLE OPTIONS below.
|
||||||
|
|
||||||
--allow-empty-message::
|
--allow-empty-message::
|
||||||
No-op. Rebasing commits with an empty message used to fail
|
No-op. Rebasing commits with an empty message used to fail
|
||||||
@ -590,7 +602,7 @@ are incompatible with the following options:
|
|||||||
* --preserve-merges
|
* --preserve-merges
|
||||||
* --interactive
|
* --interactive
|
||||||
* --exec
|
* --exec
|
||||||
* --keep-empty
|
* --no-keep-empty
|
||||||
* --empty=
|
* --empty=
|
||||||
* --edit-todo
|
* --edit-todo
|
||||||
* --root when used in combination with --onto
|
* --root when used in combination with --onto
|
||||||
@ -623,12 +635,15 @@ commits that started empty, though these are rare in practice. It
|
|||||||
also drops commits that become empty and has no option for controlling
|
also drops commits that become empty and has no option for controlling
|
||||||
this behavior.
|
this behavior.
|
||||||
|
|
||||||
The merge backend keeps intentionally empty commits. Similar to the
|
The merge backend keeps intentionally empty commits by default (though
|
||||||
apply backend, by default the merge backend drops commits that become
|
with -i they are marked as empty in the todo list editor, or they can
|
||||||
empty unless -i/--interactive is specified (in which case it stops and
|
be dropped automatically with --no-keep-empty).
|
||||||
asks the user what to do). The merge backend also has an
|
|
||||||
--empty={drop,keep,ask} option for changing the behavior of handling
|
Similar to the apply backend, by default the merge backend drops
|
||||||
commits that become empty.
|
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 detection
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -85,6 +85,7 @@ struct rebase_options {
|
|||||||
const char *action;
|
const char *action;
|
||||||
int signoff;
|
int signoff;
|
||||||
int allow_rerere_autoupdate;
|
int allow_rerere_autoupdate;
|
||||||
|
int keep_empty;
|
||||||
int autosquash;
|
int autosquash;
|
||||||
char *gpg_sign_opt;
|
char *gpg_sign_opt;
|
||||||
int autostash;
|
int autostash;
|
||||||
@ -100,6 +101,7 @@ struct rebase_options {
|
|||||||
#define REBASE_OPTIONS_INIT { \
|
#define REBASE_OPTIONS_INIT { \
|
||||||
.type = REBASE_UNSPECIFIED, \
|
.type = REBASE_UNSPECIFIED, \
|
||||||
.empty = EMPTY_UNSPECIFIED, \
|
.empty = EMPTY_UNSPECIFIED, \
|
||||||
|
.keep_empty = 1, \
|
||||||
.default_backend = "merge", \
|
.default_backend = "merge", \
|
||||||
.flags = REBASE_NO_QUIET, \
|
.flags = REBASE_NO_QUIET, \
|
||||||
.git_am_opts = ARGV_ARRAY_INIT, \
|
.git_am_opts = ARGV_ARRAY_INIT, \
|
||||||
@ -379,6 +381,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
|
|||||||
|
|
||||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
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 |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
|
||||||
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
||||||
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
||||||
@ -442,6 +445,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void imply_merge(struct rebase_options *opts, const char *option);
|
||||||
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
|
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
|
||||||
int unset)
|
int unset)
|
||||||
{
|
{
|
||||||
@ -449,10 +453,8 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
|
|||||||
|
|
||||||
BUG_ON_OPT_ARG(arg);
|
BUG_ON_OPT_ARG(arg);
|
||||||
|
|
||||||
/*
|
imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
|
||||||
* If we ever want to remap --keep-empty to --empty=keep, insert:
|
opts->keep_empty = !unset;
|
||||||
* opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
|
|
||||||
*/
|
|
||||||
opts->type = REBASE_MERGE;
|
opts->type = REBASE_MERGE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -471,7 +473,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
|
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
|
||||||
REBASE_FORCE),
|
REBASE_FORCE),
|
||||||
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
||||||
N_("(DEPRECATED) keep empty commits"),
|
N_("keep commits which start empty"),
|
||||||
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
||||||
parse_opt_keep_empty },
|
parse_opt_keep_empty },
|
||||||
OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
|
OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
|
||||||
@ -559,7 +561,7 @@ static void imply_merge(struct rebase_options *opts, const char *option)
|
|||||||
{
|
{
|
||||||
switch (opts->type) {
|
switch (opts->type) {
|
||||||
case REBASE_APPLY:
|
case REBASE_APPLY:
|
||||||
die(_("%s requires an interactive rebase"), option);
|
die(_("%s requires the merge backend"), option);
|
||||||
break;
|
break;
|
||||||
case REBASE_MERGE:
|
case REBASE_MERGE:
|
||||||
case REBASE_PRESERVE_MERGES:
|
case REBASE_PRESERVE_MERGES:
|
||||||
@ -1163,6 +1165,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
|
|||||||
opts->allow_rerere_autoupdate ?
|
opts->allow_rerere_autoupdate ?
|
||||||
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
|
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
|
||||||
"--rerere-autoupdate" : "--no-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, "autosquash", opts->autosquash ? "t" : "");
|
||||||
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
|
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
|
||||||
add_var(&script_snippet, "cmd", opts->cmd);
|
add_var(&script_snippet, "cmd", opts->cmd);
|
||||||
@ -1548,7 +1551,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
N_("how to handle commits that become empty"),
|
N_("how to handle commits that become empty"),
|
||||||
PARSE_OPT_NONEG, parse_opt_empty),
|
PARSE_OPT_NONEG, parse_opt_empty),
|
||||||
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
||||||
N_("(DEPRECATED) keep empty commits"),
|
N_("keep commits which start empty"),
|
||||||
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
||||||
parse_opt_keep_empty },
|
parse_opt_keep_empty },
|
||||||
OPT_BOOL(0, "autosquash", &options.autosquash,
|
OPT_BOOL(0, "autosquash", &options.autosquash,
|
||||||
|
11
sequencer.c
11
sequencer.c
@ -4615,6 +4615,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
|
|||||||
struct rev_info *revs, struct strbuf *out,
|
struct rev_info *revs, struct strbuf *out,
|
||||||
unsigned flags)
|
unsigned flags)
|
||||||
{
|
{
|
||||||
|
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
|
||||||
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
|
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
|
||||||
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
|
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
|
||||||
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
|
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
|
||||||
@ -4669,6 +4670,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
|
|||||||
is_empty = is_original_commit_empty(commit);
|
is_empty = is_original_commit_empty(commit);
|
||||||
if (!is_empty && (commit->object.flags & PATCHSAME))
|
if (!is_empty && (commit->object.flags & PATCHSAME))
|
||||||
continue;
|
continue;
|
||||||
|
if (is_empty && !keep_empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
strbuf_reset(&oneline);
|
strbuf_reset(&oneline);
|
||||||
pretty_print_commit(pp, commit, &oneline);
|
pretty_print_commit(pp, commit, &oneline);
|
||||||
@ -4680,6 +4683,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
|
|||||||
strbuf_addf(&buf, "%s %s %s", cmd_pick,
|
strbuf_addf(&buf, "%s %s %s", cmd_pick,
|
||||||
oid_to_hex(&commit->object.oid),
|
oid_to_hex(&commit->object.oid),
|
||||||
oneline.buf);
|
oneline.buf);
|
||||||
|
if (is_empty)
|
||||||
|
strbuf_addf(&buf, " %c empty",
|
||||||
|
comment_line_char);
|
||||||
|
|
||||||
FLEX_ALLOC_STR(entry, string, buf.buf);
|
FLEX_ALLOC_STR(entry, string, buf.buf);
|
||||||
oidcpy(&entry->entry.oid, &commit->object.oid);
|
oidcpy(&entry->entry.oid, &commit->object.oid);
|
||||||
@ -4843,6 +4849,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
|
|||||||
struct pretty_print_context pp = {0};
|
struct pretty_print_context pp = {0};
|
||||||
struct rev_info revs;
|
struct rev_info revs;
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
|
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
|
||||||
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
|
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
|
||||||
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
|
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
|
||||||
|
|
||||||
@ -4882,9 +4889,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
|
|||||||
|
|
||||||
if (!is_empty && (commit->object.flags & PATCHSAME))
|
if (!is_empty && (commit->object.flags & PATCHSAME))
|
||||||
continue;
|
continue;
|
||||||
|
if (is_empty && !keep_empty)
|
||||||
|
continue;
|
||||||
strbuf_addf(out, "%s %s ", insn,
|
strbuf_addf(out, "%s %s ", insn,
|
||||||
oid_to_hex(&commit->object.oid));
|
oid_to_hex(&commit->object.oid));
|
||||||
pretty_print_commit(&pp, commit, out);
|
pretty_print_commit(&pp, commit, out);
|
||||||
|
if (is_empty)
|
||||||
|
strbuf_addf(out, " %c empty", comment_line_char);
|
||||||
strbuf_addch(out, '\n');
|
strbuf_addch(out, '\n');
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -134,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_skip(struct repository *repo, struct replay_opts *opts);
|
||||||
int sequencer_remove_state(struct replay_opts *opts);
|
int sequencer_remove_state(struct replay_opts *opts);
|
||||||
|
|
||||||
/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
|
#define TODO_LIST_KEEP_EMPTY (1U << 0)
|
||||||
#define TODO_LIST_SHORTEN_IDS (1U << 1)
|
#define TODO_LIST_SHORTEN_IDS (1U << 1)
|
||||||
#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
|
#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
|
||||||
#define TODO_LIST_REBASE_MERGES (1U << 3)
|
#define TODO_LIST_REBASE_MERGES (1U << 3)
|
||||||
|
@ -220,14 +220,13 @@ test_have_prereq !REBASE_P || test_run_rebase failure -p
|
|||||||
test_run_rebase () {
|
test_run_rebase () {
|
||||||
result=$1
|
result=$1
|
||||||
shift
|
shift
|
||||||
test_expect_$result "rebase $* --keep-empty" "
|
test_expect_$result "rebase $* --no-keep-empty drops begin-empty commits" "
|
||||||
reset_rebase &&
|
reset_rebase &&
|
||||||
git rebase $* --keep-empty c l &&
|
git rebase $* --no-keep-empty c l &&
|
||||||
test_cmp_rev c HEAD~3 &&
|
test_cmp_rev c HEAD~2 &&
|
||||||
test_linear_range 'd k l' c..
|
test_linear_range 'd l' c..
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
test_run_rebase success --apply
|
|
||||||
test_run_rebase success -m
|
test_run_rebase success -m
|
||||||
test_run_rebase success -i
|
test_run_rebase success -i
|
||||||
test_have_prereq !REBASE_P || test_run_rebase success -p
|
test_have_prereq !REBASE_P || test_run_rebase success -p
|
||||||
@ -242,7 +241,6 @@ test_run_rebase () {
|
|||||||
test_linear_range 'd k l' j..
|
test_linear_range 'd k l' j..
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
test_run_rebase success --apply
|
|
||||||
test_run_rebase success -m
|
test_run_rebase success -m
|
||||||
test_run_rebase success -i
|
test_run_rebase success -i
|
||||||
test_have_prereq !REBASE_P || test_run_rebase success -p
|
test_have_prereq !REBASE_P || test_run_rebase success -p
|
||||||
|
@ -123,6 +123,42 @@ test_expect_success 'rebase --interactive uses default of --empty=ask' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rebase --merge --empty=drop --keep-empty' '
|
||||||
|
git checkout -B testing localmods &&
|
||||||
|
git rebase --merge --empty=drop --keep-empty 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 --no-keep-empty' '
|
||||||
|
git checkout -B testing localmods &&
|
||||||
|
git rebase --merge --empty=drop --no-keep-empty upstream &&
|
||||||
|
|
||||||
|
test_write_lines C B A >expect &&
|
||||||
|
git log --format=%s >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rebase --merge --empty=keep --keep-empty' '
|
||||||
|
git checkout -B testing localmods &&
|
||||||
|
git rebase --merge --empty=keep --keep-empty 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=keep --no-keep-empty' '
|
||||||
|
git checkout -B testing localmods &&
|
||||||
|
git rebase --merge --empty=keep --no-keep-empty upstream &&
|
||||||
|
|
||||||
|
test_write_lines C2 C B A >expect &&
|
||||||
|
git log --format=%s >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'rebase --merge does not leave state laying around' '
|
test_expect_success 'rebase --merge does not leave state laying around' '
|
||||||
git checkout -B testing localmods~2 &&
|
git checkout -B testing localmods~2 &&
|
||||||
git rebase --merge upstream &&
|
git rebase --merge upstream &&
|
||||||
|
Reference in New Issue
Block a user