Skip to content

Commit 3e50dfd

Browse files
committed
Merge branch 'pw/rebase-i-error-message' into maint-2.45
When the user adds to "git rebase -i" instruction to "pick" a merge commit, the error experience is not pleasant. Such an error is now caught earlier in the process that parses the todo list. * pw/rebase-i-error-message: rebase -i: improve error message when picking merge rebase -i: pass struct replay_opts to parse_insn_line()
2 parents f13710e + 4c063c8 commit 3e50dfd

File tree

9 files changed

+153
-30
lines changed

9 files changed

+153
-30
lines changed

Documentation/config/advice.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ advice.*::
9696
`pushNonFFCurrent`, `pushNonFFMatching`, `pushAlreadyExists`,
9797
`pushFetchFirst`, `pushNeedsForce`, and `pushRefNeedsUpdate`
9898
simultaneously.
99+
rebaseTodoError::
100+
Shown when there is an error after editing the rebase todo list.
99101
refSyntax::
100102
Shown when the user provides an illegal ref name, to
101103
tell the user about the ref syntax documentation.

advice.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ static struct {
6969
[ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" },
7070
[ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" },
7171
[ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */
72+
[ADVICE_REBASE_TODO_ERROR] = { "rebaseTodoError" },
7273
[ADVICE_REF_SYNTAX] = { "refSyntax" },
7374
[ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh" },
7475
[ADVICE_RESOLVE_CONFLICT] = { "resolveConflict" },

advice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum advice_type {
3737
ADVICE_PUSH_UNQUALIFIED_REF_NAME,
3838
ADVICE_PUSH_UPDATE_REJECTED,
3939
ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
40+
ADVICE_REBASE_TODO_ERROR,
4041
ADVICE_REF_SYNTAX,
4142
ADVICE_RESET_NO_REFRESH_WARNING,
4243
ADVICE_RESOLVE_CONFLICT,

builtin/rebase.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
194194
return replay;
195195
}
196196

197-
static int edit_todo_file(unsigned flags)
197+
static int edit_todo_file(unsigned flags, struct replay_opts *opts)
198198
{
199199
const char *todo_file = rebase_path_todo();
200200
struct todo_list todo_list = TODO_LIST_INIT,
@@ -205,7 +205,8 @@ static int edit_todo_file(unsigned flags)
205205
return error_errno(_("could not read '%s'."), todo_file);
206206

207207
strbuf_stripspace(&todo_list.buf, comment_line_str);
208-
res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
208+
res = edit_todo_list(the_repository, opts, &todo_list, &new_todo,
209+
NULL, NULL, flags);
209210
if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
210211
NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
211212
res = error_errno(_("could not write '%s'"), todo_file);
@@ -296,8 +297,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
296297
error(_("could not generate todo list"));
297298
else {
298299
discard_index(&the_index);
299-
if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
300-
&todo_list))
300+
if (todo_list_parse_insn_buffer(the_repository, &replay,
301+
todo_list.buf.buf, &todo_list))
301302
BUG("unusable todo list");
302303

303304
ret = complete_action(the_repository, &replay, flags,
@@ -352,9 +353,13 @@ static int run_sequencer_rebase(struct rebase_options *opts)
352353
replay_opts_release(&replay_opts);
353354
break;
354355
}
355-
case ACTION_EDIT_TODO:
356-
ret = edit_todo_file(flags);
356+
case ACTION_EDIT_TODO: {
357+
struct replay_opts replay_opts = get_replay_opts(opts);
358+
359+
ret = edit_todo_file(flags, &replay_opts);
360+
replay_opts_release(&replay_opts);
357361
break;
362+
}
358363
case ACTION_SHOW_CURRENT_PATCH: {
359364
struct child_process cmd = CHILD_PROCESS_INIT;
360365

rebase-interactive.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ void append_todo_help(int command_count,
101101
strbuf_add_commented_lines(buf, msg, strlen(msg), comment_line_str);
102102
}
103103

104-
int edit_todo_list(struct repository *r, struct todo_list *todo_list,
105-
struct todo_list *new_todo, const char *shortrevisions,
106-
const char *shortonto, unsigned flags)
104+
int edit_todo_list(struct repository *r, struct replay_opts *opts,
105+
struct todo_list *todo_list, struct todo_list *new_todo,
106+
const char *shortrevisions, const char *shortonto,
107+
unsigned flags)
107108
{
108109
const char *todo_file = rebase_path_todo(),
109110
*todo_backup = rebase_path_todo_backup();
@@ -114,7 +115,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,
114115
* it. If there is an error, we do not return, because the user
115116
* might want to fix it in the first place. */
116117
if (!initial)
117-
incorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |
118+
incorrect = todo_list_parse_insn_buffer(r, opts,
119+
todo_list->buf.buf,
120+
todo_list) |
118121
file_exists(rebase_path_dropped());
119122

120123
if (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,
@@ -134,13 +137,13 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,
134137
if (initial && new_todo->buf.len == 0)
135138
return -3;
136139

137-
if (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {
140+
if (todo_list_parse_insn_buffer(r, opts, new_todo->buf.buf, new_todo)) {
138141
fprintf(stderr, _(edit_todo_list_advice));
139142
return -4;
140143
}
141144

142145
if (incorrect) {
143-
if (todo_list_check_against_backup(r, new_todo)) {
146+
if (todo_list_check_against_backup(r, opts, new_todo)) {
144147
write_file(rebase_path_dropped(), "%s", "");
145148
return -4;
146149
}
@@ -228,13 +231,15 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)
228231
return res;
229232
}
230233

231-
int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)
234+
int todo_list_check_against_backup(struct repository *r,
235+
struct replay_opts *opts,
236+
struct todo_list *todo_list)
232237
{
233238
struct todo_list backup = TODO_LIST_INIT;
234239
int res = 0;
235240

236241
if (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {
237-
todo_list_parse_insn_buffer(r, backup.buf.buf, &backup);
242+
todo_list_parse_insn_buffer(r, opts, backup.buf.buf, &backup);
238243
res = todo_list_check(&backup, todo_list);
239244
}
240245

rebase-interactive.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33

44
struct strbuf;
55
struct repository;
6+
struct replay_opts;
67
struct todo_list;
78

89
void append_todo_help(int command_count,
910
const char *shortrevisions, const char *shortonto,
1011
struct strbuf *buf);
11-
int edit_todo_list(struct repository *r, struct todo_list *todo_list,
12-
struct todo_list *new_todo, const char *shortrevisions,
13-
const char *shortonto, unsigned flags);
12+
int edit_todo_list(struct repository *r, struct replay_opts *opts,
13+
struct todo_list *todo_list, struct todo_list *new_todo,
14+
const char *shortrevisions, const char *shortonto,
15+
unsigned flags);
1416

1517
int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);
1618
int todo_list_check_against_backup(struct repository *r,
19+
struct replay_opts *opts,
1720
struct todo_list *todo_list);
1821

1922
#endif

sequencer.c

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,8 +2626,63 @@ static int check_label_or_ref_arg(enum todo_command command, const char *arg)
26262626
return 0;
26272627
}
26282628

2629-
static int parse_insn_line(struct repository *r, struct todo_item *item,
2630-
const char *buf, const char *bol, char *eol)
2629+
static int check_merge_commit_insn(enum todo_command command)
2630+
{
2631+
switch(command) {
2632+
case TODO_PICK:
2633+
error(_("'%s' does not accept merge commits"),
2634+
todo_command_info[command].str);
2635+
advise_if_enabled(ADVICE_REBASE_TODO_ERROR, _(
2636+
/*
2637+
* TRANSLATORS: 'pick' and 'merge -C' should not be
2638+
* translated.
2639+
*/
2640+
"'pick' does not take a merge commit. If you wanted to\n"
2641+
"replay the merge, use 'merge -C' on the commit."));
2642+
return -1;
2643+
2644+
case TODO_REWORD:
2645+
error(_("'%s' does not accept merge commits"),
2646+
todo_command_info[command].str);
2647+
advise_if_enabled(ADVICE_REBASE_TODO_ERROR, _(
2648+
/*
2649+
* TRANSLATORS: 'reword' and 'merge -c' should not be
2650+
* translated.
2651+
*/
2652+
"'reword' does not take a merge commit. If you wanted to\n"
2653+
"replay the merge and reword the commit message, use\n"
2654+
"'merge -c' on the commit"));
2655+
return -1;
2656+
2657+
case TODO_EDIT:
2658+
error(_("'%s' does not accept merge commits"),
2659+
todo_command_info[command].str);
2660+
advise_if_enabled(ADVICE_REBASE_TODO_ERROR, _(
2661+
/*
2662+
* TRANSLATORS: 'edit', 'merge -C' and 'break' should
2663+
* not be translated.
2664+
*/
2665+
"'edit' does not take a merge commit. If you wanted to\n"
2666+
"replay the merge, use 'merge -C' on the commit, and then\n"
2667+
"'break' to give the control back to you so that you can\n"
2668+
"do 'git commit --amend && git rebase --continue'."));
2669+
return -1;
2670+
2671+
case TODO_FIXUP:
2672+
case TODO_SQUASH:
2673+
return error(_("cannot squash merge commit into another commit"));
2674+
2675+
case TODO_MERGE:
2676+
return 0;
2677+
2678+
default:
2679+
BUG("unexpected todo_command");
2680+
}
2681+
}
2682+
2683+
static int parse_insn_line(struct repository *r, struct replay_opts *opts,
2684+
struct todo_item *item, const char *buf,
2685+
const char *bol, char *eol)
26312686
{
26322687
struct object_id commit_oid;
26332688
char *end_of_object_name;
@@ -2731,7 +2786,12 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
27312786
return status;
27322787

27332788
item->commit = lookup_commit_reference(r, &commit_oid);
2734-
return item->commit ? 0 : -1;
2789+
if (!item->commit)
2790+
return -1;
2791+
if (is_rebase_i(opts) &&
2792+
item->commit->parents && item->commit->parents->next)
2793+
return check_merge_commit_insn(item->command);
2794+
return 0;
27352795
}
27362796

27372797
int sequencer_get_last_command(struct repository *r UNUSED, enum replay_action *action)
@@ -2761,8 +2821,8 @@ int sequencer_get_last_command(struct repository *r UNUSED, enum replay_action *
27612821
return ret;
27622822
}
27632823

2764-
int todo_list_parse_insn_buffer(struct repository *r, char *buf,
2765-
struct todo_list *todo_list)
2824+
int todo_list_parse_insn_buffer(struct repository *r, struct replay_opts *opts,
2825+
char *buf, struct todo_list *todo_list)
27662826
{
27672827
struct todo_item *item;
27682828
char *p = buf, *next_p;
@@ -2780,7 +2840,7 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf,
27802840

27812841
item = append_new_todo(todo_list);
27822842
item->offset_in_buf = p - todo_list->buf.buf;
2783-
if (parse_insn_line(r, item, buf, p, eol)) {
2843+
if (parse_insn_line(r, opts, item, buf, p, eol)) {
27842844
res = error(_("invalid line %d: %.*s"),
27852845
i, (int)(eol - p), p);
27862846
item->command = TODO_COMMENT + 1;
@@ -2930,7 +2990,7 @@ static int read_populate_todo(struct repository *r,
29302990
if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
29312991
return -1;
29322992

2933-
res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
2993+
res = todo_list_parse_insn_buffer(r, opts, todo_list->buf.buf, todo_list);
29342994
if (res) {
29352995
if (is_rebase_i(opts))
29362996
return error(_("please fix this using "
@@ -2960,7 +3020,7 @@ static int read_populate_todo(struct repository *r,
29603020
struct todo_list done = TODO_LIST_INIT;
29613021

29623022
if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
2963-
!todo_list_parse_insn_buffer(r, done.buf.buf, &done))
3023+
!todo_list_parse_insn_buffer(r, opts, done.buf.buf, &done))
29643024
todo_list->done_nr = count_commands(&done);
29653025
else
29663026
todo_list->done_nr = 0;
@@ -5317,7 +5377,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
53175377
goto release_todo_list;
53185378

53195379
if (file_exists(rebase_path_dropped())) {
5320-
if ((res = todo_list_check_against_backup(r, &todo_list)))
5380+
if ((res = todo_list_check_against_backup(r, opts,
5381+
&todo_list)))
53215382
goto release_todo_list;
53225383

53235384
unlink(rebase_path_dropped());
@@ -6360,7 +6421,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
63606421
return error(_("nothing to do"));
63616422
}
63626423

6363-
res = edit_todo_list(r, todo_list, &new_todo, shortrevisions,
6424+
res = edit_todo_list(r, opts, todo_list, &new_todo, shortrevisions,
63646425
shortonto, flags);
63656426
if (res == -1)
63666427
return -1;
@@ -6388,7 +6449,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
63886449
strbuf_release(&buf2);
63896450
/* Nothing is done yet, and we're reparsing, so let's reset the count */
63906451
new_todo.total_nr = 0;
6391-
if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0)
6452+
if (todo_list_parse_insn_buffer(r, opts, new_todo.buf.buf, &new_todo) < 0)
63926453
BUG("invalid todo list after expanding IDs:\n%s",
63936454
new_todo.buf.buf);
63946455

sequencer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ struct todo_list {
136136
.buf = STRBUF_INIT, \
137137
}
138138

139-
int todo_list_parse_insn_buffer(struct repository *r, char *buf,
140-
struct todo_list *todo_list);
139+
int todo_list_parse_insn_buffer(struct repository *r, struct replay_opts *opts,
140+
char *buf, struct todo_list *todo_list);
141141
int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
142142
const char *file, const char *shortrevisions,
143143
const char *shortonto, int num, unsigned flags);

t/t3404-rebase-interactive.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,51 @@ test_expect_success 'bad labels and refs rejected when parsing todo list' '
22152215
test_path_is_missing execed
22162216
'
22172217

2218+
test_expect_success 'non-merge commands reject merge commits' '
2219+
test_when_finished "test_might_fail git rebase --abort" &&
2220+
git checkout E &&
2221+
git merge I &&
2222+
oid=$(git rev-parse HEAD) &&
2223+
cat >todo <<-EOF &&
2224+
pick $oid
2225+
reword $oid
2226+
edit $oid
2227+
fixup $oid
2228+
squash $oid
2229+
EOF
2230+
(
2231+
set_replace_editor todo &&
2232+
test_must_fail git rebase -i HEAD 2>actual
2233+
) &&
2234+
cat >expect <<-EOF &&
2235+
error: ${SQ}pick${SQ} does not accept merge commits
2236+
hint: ${SQ}pick${SQ} does not take a merge commit. If you wanted to
2237+
hint: replay the merge, use ${SQ}merge -C${SQ} on the commit.
2238+
hint: Disable this message with "git config advice.rebaseTodoError false"
2239+
error: invalid line 1: pick $oid
2240+
error: ${SQ}reword${SQ} does not accept merge commits
2241+
hint: ${SQ}reword${SQ} does not take a merge commit. If you wanted to
2242+
hint: replay the merge and reword the commit message, use
2243+
hint: ${SQ}merge -c${SQ} on the commit
2244+
hint: Disable this message with "git config advice.rebaseTodoError false"
2245+
error: invalid line 2: reword $oid
2246+
error: ${SQ}edit${SQ} does not accept merge commits
2247+
hint: ${SQ}edit${SQ} does not take a merge commit. If you wanted to
2248+
hint: replay the merge, use ${SQ}merge -C${SQ} on the commit, and then
2249+
hint: ${SQ}break${SQ} to give the control back to you so that you can
2250+
hint: do ${SQ}git commit --amend && git rebase --continue${SQ}.
2251+
hint: Disable this message with "git config advice.rebaseTodoError false"
2252+
error: invalid line 3: edit $oid
2253+
error: cannot squash merge commit into another commit
2254+
error: invalid line 4: fixup $oid
2255+
error: cannot squash merge commit into another commit
2256+
error: invalid line 5: squash $oid
2257+
You can fix this with ${SQ}git rebase --edit-todo${SQ} and then run ${SQ}git rebase --continue${SQ}.
2258+
Or you can abort the rebase with ${SQ}git rebase --abort${SQ}.
2259+
EOF
2260+
test_cmp expect actual
2261+
'
2262+
22182263
# This must be the last test in this file
22192264
test_expect_success '$EDITOR and friends are unchanged' '
22202265
test_editor_unchanged

0 commit comments

Comments
 (0)