Skip to content

Commit d48e5e2

Browse files
newrengitster
authored andcommitted
rebase (interactive-backend): make --keep-empty the default
Different rebase backends have different treatment for commits which start empty (i.e. have no changes relative to their parent), and the --keep-empty option was added at some point to allow adjusting behavior. The handling of commits which start empty is actually quite similar to commit b00bf1c (git-rebase: make --allow-empty-message the default, 2018-06-27), which pointed out that the behavior for various backends is often more happenstance than design. The specific change made in that commit is actually quite relevant as well and much of the logic there directly applies here. It makes a lot of sense in 'git commit' to error out on the creation of empty commits, unless an override flag is provided. However, once someone determines that there is a rare case that merits using the manual override to create such a commit, it is somewhere between annoying and harmful to have to take extra steps to keep such intentional commits around. Granted, empty commits are quite rare, which is why handling of them doesn't get considered much and folks tend to defer to existing (accidental) behavior and assume there was a reason for it, leading them to just add flags (--keep-empty in this case) that allow them to override the bad defaults. Fix the interactive backend so that --keep-empty is the default, much like we did with --allow-empty-message. The am backend should also be fixed to have --keep-empty semantics for commits that start empty, but that is not included in this patch other than a testcase documenting the failure. Note that there was one test in t3421 which appears to have been written expecting --keep-empty to not be the default as correct behavior. This test was introduced in commit 00b8be5 ("add tests for rebasing of empty commits", 2013-06-06), which was part of a series focusing on rebase topology and which had an interesting original cover letter at https://lore.kernel.org/git/[email protected]/ which noted Your input especially appreciated on whether you agree with the intent of the test cases. and then went into a long example about how one of the many tests added had several questions about whether it was correct. As such, I believe most the tests in that series were about testing rebase topology with as many different flags as possible and were not trying to state in general how those flags should behave otherwise. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a9ae8fd commit d48e5e2

File tree

9 files changed

+126
-55
lines changed

9 files changed

+126
-55
lines changed

Documentation/git-rebase.txt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below.
259259
unchanged as a result.
260260

261261
--keep-empty::
262-
Keep the commits that do not change anything from its
263-
parents in the result.
262+
No-op. Rebasing commits that started empty (had no change
263+
relative to their parent) used to fail and this option would
264+
override that behavior, allowing commits with empty changes to
265+
be rebased. Now commits with no changes do not cause rebasing
266+
to halt.
264267
+
265-
See also INCOMPATIBLE OPTIONS below.
268+
See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
266269

267270
--allow-empty-message::
268271
No-op. Rebasing commits with an empty message used to fail
@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave.
577580
Empty commits
578581
~~~~~~~~~~~~~
579582

580-
The am backend drops any "empty" commits, regardless of whether the
581-
commit started empty (had no changes relative to its parent to
582-
start with) or ended empty (all changes were already applied
583-
upstream in other commits).
583+
The am backend unfortunately drops intentionally empty commits, i.e.
584+
commits that started empty, though these are rare in practice. It
585+
also drops commits that become empty and has no option for controlling
586+
this behavior.
584587

585-
The interactive backend drops commits by default that
586-
started empty and halts if it hits a commit that ended up empty.
587-
The `--keep-empty` option exists for the interactive backend to allow
588-
it to keep commits that started empty.
588+
The interactive backend keeps intentionally empty commits.
589+
Unfortunately, it always halts whenever it runs across a commit that
590+
becomes empty, even when the rebase is not explicitly interactive.
589591

590592
Directory rename detection
591593
~~~~~~~~~~~~~~~~~~~~~~~~~~

builtin/rebase.c

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ struct rebase_options {
7777
const char *action;
7878
int signoff;
7979
int allow_rerere_autoupdate;
80-
int keep_empty;
8180
int autosquash;
8281
char *gpg_sign_opt;
8382
int autostash;
@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
375374

376375
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
377376

378-
flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
379377
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
380378
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
381379
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,
439437
return ret;
440438
}
441439

440+
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
441+
int unset)
442+
{
443+
struct rebase_options *opts = opt->value;
444+
445+
BUG_ON_OPT_ARG(arg);
446+
447+
opts->type = REBASE_INTERACTIVE;
448+
return 0;
449+
}
450+
442451
static const char * const builtin_rebase_interactive_usage[] = {
443452
N_("git rebase--interactive [<options>]"),
444453
NULL
@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
452461
struct option options[] = {
453462
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
454463
REBASE_FORCE),
455-
OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
464+
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
465+
N_("(DEPRECATED) keep empty commits"),
466+
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
467+
parse_opt_keep_empty },
456468
OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
457469
N_("allow commits with empty messages"),
458470
PARSE_OPT_HIDDEN),
@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
11451157
opts->allow_rerere_autoupdate ?
11461158
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
11471159
"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
1148-
add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
11491160
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
11501161
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
11511162
add_var(&script_snippet, "cmd", opts->cmd);
@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
14831494
"ignoring them"),
14841495
REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
14851496
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
1486-
OPT_BOOL('k', "keep-empty", &options.keep_empty,
1487-
N_("preserve empty commits during rebase")),
1497+
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
1498+
N_("(DEPRECATED) keep empty commits"),
1499+
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
1500+
parse_opt_keep_empty },
14881501
OPT_BOOL(0, "autosquash", &options.autosquash,
14891502
N_("move commits that begin with "
14901503
"squash!/fixup! under -i")),
@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
17471760
if (!(options.flags & REBASE_NO_QUIET))
17481761
argv_array_push(&options.git_am_opts, "-q");
17491762

1750-
if (options.keep_empty)
1751-
imply_interactive(&options, "--keep-empty");
1752-
17531763
if (gpg_sign) {
17541764
free(options.gpg_sign_opt);
17551765
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);

rebase-interactive.c

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)
2828
return MISSING_COMMIT_CHECK_IGNORE;
2929
}
3030

31-
void append_todo_help(unsigned keep_empty, int command_count,
31+
void append_todo_help(int command_count,
3232
const char *shortrevisions, const char *shortonto,
3333
struct strbuf *buf)
3434
{
@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count,
8080
"the rebase will be aborted.\n\n");
8181

8282
strbuf_add_commented_lines(buf, msg, strlen(msg));
83-
84-
if (!keep_empty) {
85-
msg = _("Note that empty commits are commented out");
86-
strbuf_add_commented_lines(buf, msg, strlen(msg));
87-
}
8883
}
8984

9085
int edit_todo_list(struct repository *r, struct todo_list *todo_list,

rebase-interactive.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ struct strbuf;
55
struct repository;
66
struct todo_list;
77

8-
void append_todo_help(unsigned keep_empty, int command_count,
8+
void append_todo_help(int command_count,
99
const char *shortrevisions, const char *shortonto,
1010
struct strbuf *buf);
1111
int edit_todo_list(struct repository *r, struct todo_list *todo_list,

sequencer.c

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,
14891489
struct replay_opts *opts,
14901490
struct commit *commit)
14911491
{
1492-
int index_unchanged, empty_commit;
1492+
int index_unchanged, originally_empty;
14931493

14941494
/*
14951495
* Three cases:
@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,
15131513
if (opts->keep_redundant_commits)
15141514
return 1;
15151515

1516-
empty_commit = is_original_commit_empty(commit);
1517-
if (empty_commit < 0)
1518-
return empty_commit;
1519-
if (!empty_commit)
1516+
originally_empty = is_original_commit_empty(commit);
1517+
if (originally_empty < 0)
1518+
return originally_empty;
1519+
if (!originally_empty)
15201520
return 0;
15211521
else
15221522
return 1;
@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
45664566
struct rev_info *revs, struct strbuf *out,
45674567
unsigned flags)
45684568
{
4569-
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
45704569
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
45714570
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
45724571
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
46294628
if (!to_merge) {
46304629
/* non-merge commit: easy case */
46314630
strbuf_reset(&buf);
4632-
if (!keep_empty && is_empty)
4633-
strbuf_addf(&buf, "%c ", comment_line_char);
46344631
strbuf_addf(&buf, "%s %s %s", cmd_pick,
46354632
oid_to_hex(&commit->object.oid),
46364633
oneline.buf);
@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
47974794
struct pretty_print_context pp = {0};
47984795
struct rev_info revs;
47994796
struct commit *commit;
4800-
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
48014797
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
48024798
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
48034799

@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
48334829
return make_script_with_merges(&pp, &revs, out, flags);
48344830

48354831
while ((commit = get_revision(&revs))) {
4836-
int is_empty = is_original_commit_empty(commit);
4832+
int is_empty = is_original_commit_empty(commit);
48374833

48384834
if (!is_empty && (commit->object.flags & PATCHSAME))
48394835
continue;
4840-
if (!keep_empty && is_empty)
4841-
strbuf_addf(out, "%c ", comment_line_char);
48424836
strbuf_addf(out, "%s %s ", insn,
48434837
oid_to_hex(&commit->object.oid));
48444838
pretty_print_commit(&pp, commit, out);
@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
49754969

49764970
todo_list_to_strbuf(r, todo_list, &buf, num, flags);
49774971
if (flags & TODO_LIST_APPEND_TODO_HELP)
4978-
append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
4972+
append_todo_help(count_commands(todo_list),
49794973
shortrevisions, shortonto, &buf);
49804974

49814975
res = write_message(buf.buf, buf.len, file, 0);

sequencer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
132132
int sequencer_skip(struct repository *repo, struct replay_opts *opts);
133133
int sequencer_remove_state(struct replay_opts *opts);
134134

135-
#define TODO_LIST_KEEP_EMPTY (1U << 0)
135+
/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
136136
#define TODO_LIST_SHORTEN_IDS (1U << 1)
137137
#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
138138
#define TODO_LIST_REBASE_MERGES (1U << 3)

t/t3421-rebase-topology-linear.sh

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' '
205205
test_run_rebase () {
206206
result=$1
207207
shift
208-
test_expect_$result "rebase $* drops empty commit" "
208+
test_expect_$result "rebase $* keeps begin-empty commits" "
209209
reset_rebase &&
210-
git rebase $* c l &&
211-
test_cmp_rev c HEAD~2 &&
212-
test_linear_range 'd l' c..
210+
git rebase $* j l &&
211+
test_cmp_rev c HEAD~4 &&
212+
test_linear_range 'j d k l' c..
213213
"
214214
}
215-
test_run_rebase success ''
215+
test_run_rebase failure ''
216216
test_run_rebase success -m
217217
test_run_rebase success -i
218-
test_have_prereq !REBASE_P || test_run_rebase success -p
218+
test_have_prereq !REBASE_P || test_run_rebase failure -p
219219

220220
test_run_rebase () {
221221
result=$1
@@ -230,7 +230,7 @@ test_run_rebase () {
230230
test_run_rebase success ''
231231
test_run_rebase success -m
232232
test_run_rebase success -i
233-
test_have_prereq !REBASE_P || test_run_rebase failure -p
233+
test_have_prereq !REBASE_P || test_run_rebase success -p
234234

235235
test_run_rebase () {
236236
result=$1
@@ -245,7 +245,7 @@ test_run_rebase () {
245245
test_run_rebase success ''
246246
test_run_rebase success -m
247247
test_run_rebase success -i
248-
test_have_prereq !REBASE_P || test_run_rebase failure -p
248+
test_have_prereq !REBASE_P || test_run_rebase success -p
249249
test_run_rebase success --rebase-merges
250250

251251
# m

t/t3424-rebase-empty.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
3+
test_description='git rebase of commits that start or become empty'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup test repository' '
8+
test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
9+
test_write_lines A B C D E F G H I J >letters &&
10+
git add numbers letters &&
11+
git commit -m A &&
12+
13+
git branch upstream &&
14+
git branch localmods &&
15+
16+
git checkout upstream &&
17+
test_write_lines A B C D E >letters &&
18+
git add letters &&
19+
git commit -m B &&
20+
21+
test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers &&
22+
git add numbers &&
23+
git commit -m C &&
24+
25+
git checkout localmods &&
26+
test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers &&
27+
git add numbers &&
28+
git commit -m C2 &&
29+
30+
git commit --allow-empty -m D &&
31+
32+
test_write_lines A B C D E >letters &&
33+
git add letters &&
34+
git commit -m "Five letters ought to be enough for anybody"
35+
'
36+
37+
test_expect_failure 'rebase (am-backend) with a variety of empty commits' '
38+
test_when_finished "git rebase --abort" &&
39+
git checkout -B testing localmods &&
40+
# rebase (--am) should not drop commits that start empty
41+
git rebase upstream &&
42+
43+
test_write_lines D C B A >expect &&
44+
git log --format=%s >actual &&
45+
test_cmp expect actual
46+
'
47+
48+
test_expect_failure 'rebase --merge with a variety of empty commits' '
49+
test_when_finished "git rebase --abort" &&
50+
git checkout -B testing localmods &&
51+
# rebase --merge should not halt on the commit that becomes empty
52+
git rebase --merge upstream &&
53+
54+
test_write_lines D C B A >expect &&
55+
git log --format=%s >actual &&
56+
test_cmp expect actual
57+
'
58+
59+
test_expect_success 'rebase --interactive with a variety of empty commits' '
60+
git checkout -B testing localmods &&
61+
test_must_fail git rebase --interactive upstream &&
62+
63+
git rebase --skip &&
64+
65+
test_write_lines D C B A >expect &&
66+
git log --format=%s >actual &&
67+
test_cmp expect actual
68+
'
69+
70+
test_done

t/t3427-rebase-subtree.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --
8585
verbose test "$(commit_message HEAD)" = "Empty commit"
8686
'
8787

88-
test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' '
88+
test_expect_success 'Rebase -Xsubtree --onto commit' '
8989
reset_rebase &&
9090
git checkout -b rebase-onto to-rebase &&
91-
test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master &&
91+
test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master &&
9292
: first pick results in no changes &&
93-
git rebase --continue &&
93+
git rebase --skip &&
9494
verbose test "$(commit_message HEAD~2)" = "master4" &&
9595
verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
9696
verbose test "$(commit_message HEAD)" = "Empty commit"
9797
'
9898

99-
test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' '
99+
test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' '
100100
reset_rebase &&
101101
git checkout -b rebase-merges-onto to-rebase &&
102-
test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root &&
102+
test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root &&
103103
: first pick results in no changes &&
104-
git rebase --continue &&
104+
git rebase --skip &&
105105
verbose test "$(commit_message HEAD~2)" = "master4" &&
106106
verbose test "$(commit_message HEAD~)" = "files_subtree/master5" &&
107107
verbose test "$(commit_message HEAD)" = "Empty commit"

0 commit comments

Comments
 (0)