Skip to content

Commit a70f725

Browse files
committed
Merge branch 'pw/rebase-i-after-failure' into maint-2.42
Various fixes to the behaviour of "rebase -i" when the command got interrupted by conflicting changes. cf. <[email protected]> * pw/rebase-i-after-failure: rebase -i: fix adding failed command to the todo list rebase --continue: refuse to commit after failed command rebase: fix rewritten list for failed pick sequencer: factor out part of pick_commits() sequencer: use rebase_path_message() rebase -i: remove patch file after conflict resolution rebase -i: move unlink() calls
2 parents d97034b + 203573b commit a70f725

File tree

5 files changed

+228
-103
lines changed

5 files changed

+228
-103
lines changed

sequencer.c

Lines changed: 99 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
147147
* the commit object name of the corresponding patch.
148148
*/
149149
static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
150+
/*
151+
* When we stop for the user to resolve conflicts this file contains
152+
* the patch of the commit that is being picked.
153+
*/
154+
static GIT_PATH_FUNC(rebase_path_patch, "rebase-merge/patch")
150155
/*
151156
* For the post-rewrite hook, we make a list of rewritten commits and
152157
* their new sha1s. The rewritten-pending list keeps the sha1s of
@@ -3401,7 +3406,8 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
34013406
return -1;
34023407
}
34033408

3404-
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
3409+
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,
3410+
int reschedule)
34053411
{
34063412
struct lock_file todo_lock = LOCK_INIT;
34073413
const char *todo_path = get_todo_path(opts);
@@ -3411,7 +3417,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
34113417
* rebase -i writes "git-rebase-todo" without the currently executing
34123418
* command, appending it to "done" instead.
34133419
*/
3414-
if (is_rebase_i(opts))
3420+
if (is_rebase_i(opts) && !reschedule)
34153421
next++;
34163422

34173423
fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
@@ -3424,7 +3430,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
34243430
if (commit_lock_file(&todo_lock) < 0)
34253431
return error(_("failed to finalize '%s'"), todo_path);
34263432

3427-
if (is_rebase_i(opts) && next > 0) {
3433+
if (is_rebase_i(opts) && !reschedule && next > 0) {
34283434
const char *done = rebase_path_done();
34293435
int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
34303436
int ret = 0;
@@ -3505,47 +3511,46 @@ static int make_patch(struct repository *r,
35053511
struct commit *commit,
35063512
struct replay_opts *opts)
35073513
{
3508-
struct strbuf buf = STRBUF_INIT;
35093514
struct rev_info log_tree_opt;
35103515
const char *subject;
35113516
char hex[GIT_MAX_HEXSZ + 1];
35123517
int res = 0;
35133518

3519+
if (!is_rebase_i(opts))
3520+
BUG("make_patch should only be called when rebasing");
3521+
35143522
oid_to_hex_r(hex, &commit->object.oid);
35153523
if (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)
35163524
return -1;
35173525
res |= write_rebase_head(&commit->object.oid);
35183526

3519-
strbuf_addf(&buf, "%s/patch", get_dir(opts));
35203527
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
35213528
repo_init_revisions(r, &log_tree_opt, NULL);
35223529
log_tree_opt.abbrev = 0;
35233530
log_tree_opt.diff = 1;
35243531
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
35253532
log_tree_opt.disable_stdin = 1;
35263533
log_tree_opt.no_commit_id = 1;
3527-
log_tree_opt.diffopt.file = fopen(buf.buf, "w");
3534+
log_tree_opt.diffopt.file = fopen(rebase_path_patch(), "w");
35283535
log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
35293536
if (!log_tree_opt.diffopt.file)
3530-
res |= error_errno(_("could not open '%s'"), buf.buf);
3537+
res |= error_errno(_("could not open '%s'"),
3538+
rebase_path_patch());
35313539
else {
35323540
res |= log_tree_commit(&log_tree_opt, commit);
35333541
fclose(log_tree_opt.diffopt.file);
35343542
}
3535-
strbuf_reset(&buf);
35363543

3537-
strbuf_addf(&buf, "%s/message", get_dir(opts));
3538-
if (!file_exists(buf.buf)) {
3544+
if (!file_exists(rebase_path_message())) {
35393545
const char *encoding = get_commit_output_encoding();
35403546
const char *commit_buffer = repo_logmsg_reencode(r,
35413547
commit, NULL,
35423548
encoding);
35433549
find_commit_subject(commit_buffer, &subject);
3544-
res |= write_message(subject, strlen(subject), buf.buf, 1);
3550+
res |= write_message(subject, strlen(subject), rebase_path_message(), 1);
35453551
repo_unuse_commit_buffer(r, commit,
35463552
commit_buffer);
35473553
}
3548-
strbuf_release(&buf);
35493554
release_revisions(&log_tree_opt);
35503555

35513556
return res;
@@ -4163,6 +4168,7 @@ static int do_merge(struct repository *r,
41634168
if (ret < 0) {
41644169
error(_("could not even attempt to merge '%.*s'"),
41654170
merge_arg_len, arg);
4171+
unlink(git_path_merge_msg(r));
41664172
goto leave_merge;
41674173
}
41684174
/*
@@ -4650,6 +4656,68 @@ N_("Could not execute the todo command\n"
46504656
" git rebase --edit-todo\n"
46514657
" git rebase --continue\n");
46524658

4659+
static int pick_one_commit(struct repository *r,
4660+
struct todo_list *todo_list,
4661+
struct replay_opts *opts,
4662+
int *check_todo, int* reschedule)
4663+
{
4664+
int res;
4665+
struct todo_item *item = todo_list->items + todo_list->current;
4666+
const char *arg = todo_item_get_arg(todo_list, item);
4667+
if (is_rebase_i(opts))
4668+
opts->reflog_message = reflog_message(
4669+
opts, command_to_string(item->command), NULL);
4670+
4671+
res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
4672+
check_todo);
4673+
if (is_rebase_i(opts) && res < 0) {
4674+
/* Reschedule */
4675+
*reschedule = 1;
4676+
return -1;
4677+
}
4678+
if (item->command == TODO_EDIT) {
4679+
struct commit *commit = item->commit;
4680+
if (!res) {
4681+
if (!opts->verbose)
4682+
term_clear_line();
4683+
fprintf(stderr, _("Stopped at %s... %.*s\n"),
4684+
short_commit_name(commit), item->arg_len, arg);
4685+
}
4686+
return error_with_patch(r, commit,
4687+
arg, item->arg_len, opts, res, !res);
4688+
}
4689+
if (is_rebase_i(opts) && !res)
4690+
record_in_rewritten(&item->commit->object.oid,
4691+
peek_command(todo_list, 1));
4692+
if (res && is_fixup(item->command)) {
4693+
if (res == 1)
4694+
intend_to_amend();
4695+
return error_failed_squash(r, item->commit, opts,
4696+
item->arg_len, arg);
4697+
} else if (res && is_rebase_i(opts) && item->commit) {
4698+
int to_amend = 0;
4699+
struct object_id oid;
4700+
4701+
/*
4702+
* If we are rewording and have either
4703+
* fast-forwarded already, or are about to
4704+
* create a new root commit, we want to amend,
4705+
* otherwise we do not.
4706+
*/
4707+
if (item->command == TODO_REWORD &&
4708+
!repo_get_oid(r, "HEAD", &oid) &&
4709+
(oideq(&item->commit->object.oid, &oid) ||
4710+
(opts->have_squash_onto &&
4711+
oideq(&opts->squash_onto, &oid))))
4712+
to_amend = 1;
4713+
4714+
return res | error_with_patch(r, item->commit,
4715+
arg, item->arg_len, opts,
4716+
res, to_amend);
4717+
}
4718+
return res;
4719+
}
4720+
46534721
static int pick_commits(struct repository *r,
46544722
struct todo_list *todo_list,
46554723
struct replay_opts *opts)
@@ -4665,12 +4733,17 @@ static int pick_commits(struct repository *r,
46654733
if (read_and_refresh_cache(r, opts))
46664734
return -1;
46674735

4736+
unlink(rebase_path_message());
4737+
unlink(rebase_path_stopped_sha());
4738+
unlink(rebase_path_amend());
4739+
unlink(rebase_path_patch());
4740+
46684741
while (todo_list->current < todo_list->nr) {
46694742
struct todo_item *item = todo_list->items + todo_list->current;
46704743
const char *arg = todo_item_get_arg(todo_list, item);
46714744
int check_todo = 0;
46724745

4673-
if (save_todo(todo_list, opts))
4746+
if (save_todo(todo_list, opts, reschedule))
46744747
return -1;
46754748
if (is_rebase_i(opts)) {
46764749
if (item->command != TODO_COMMENT) {
@@ -4688,10 +4761,7 @@ static int pick_commits(struct repository *r,
46884761
todo_list->total_nr,
46894762
opts->verbose ? "\n" : "\r");
46904763
}
4691-
unlink(rebase_path_message());
46924764
unlink(rebase_path_author_script());
4693-
unlink(rebase_path_stopped_sha());
4694-
unlink(rebase_path_amend());
46954765
unlink(git_path_merge_head(r));
46964766
unlink(git_path_auto_merge(r));
46974767
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
@@ -4703,66 +4773,10 @@ static int pick_commits(struct repository *r,
47034773
}
47044774
}
47054775
if (item->command <= TODO_SQUASH) {
4706-
if (is_rebase_i(opts))
4707-
opts->reflog_message = reflog_message(opts,
4708-
command_to_string(item->command), NULL);
4709-
4710-
res = do_pick_commit(r, item, opts,
4711-
is_final_fixup(todo_list),
4712-
&check_todo);
4713-
if (is_rebase_i(opts) && res < 0) {
4714-
/* Reschedule */
4715-
advise(_(rescheduled_advice),
4716-
get_item_line_length(todo_list,
4717-
todo_list->current),
4718-
get_item_line(todo_list,
4719-
todo_list->current));
4720-
todo_list->current--;
4721-
if (save_todo(todo_list, opts))
4722-
return -1;
4723-
}
4724-
if (item->command == TODO_EDIT) {
4725-
struct commit *commit = item->commit;
4726-
if (!res) {
4727-
if (!opts->verbose)
4728-
term_clear_line();
4729-
fprintf(stderr,
4730-
_("Stopped at %s... %.*s\n"),
4731-
short_commit_name(commit),
4732-
item->arg_len, arg);
4733-
}
4734-
return error_with_patch(r, commit,
4735-
arg, item->arg_len, opts, res, !res);
4736-
}
4737-
if (is_rebase_i(opts) && !res)
4738-
record_in_rewritten(&item->commit->object.oid,
4739-
peek_command(todo_list, 1));
4740-
if (res && is_fixup(item->command)) {
4741-
if (res == 1)
4742-
intend_to_amend();
4743-
return error_failed_squash(r, item->commit, opts,
4744-
item->arg_len, arg);
4745-
} else if (res && is_rebase_i(opts) && item->commit) {
4746-
int to_amend = 0;
4747-
struct object_id oid;
4748-
4749-
/*
4750-
* If we are rewording and have either
4751-
* fast-forwarded already, or are about to
4752-
* create a new root commit, we want to amend,
4753-
* otherwise we do not.
4754-
*/
4755-
if (item->command == TODO_REWORD &&
4756-
!repo_get_oid(r, "HEAD", &oid) &&
4757-
(oideq(&item->commit->object.oid, &oid) ||
4758-
(opts->have_squash_onto &&
4759-
oideq(&opts->squash_onto, &oid))))
4760-
to_amend = 1;
4761-
4762-
return res | error_with_patch(r, item->commit,
4763-
arg, item->arg_len, opts,
4764-
res, to_amend);
4765-
}
4776+
res = pick_one_commit(r, todo_list, opts, &check_todo,
4777+
&reschedule);
4778+
if (!res && item->command == TODO_EDIT)
4779+
return 0;
47664780
} else if (item->command == TODO_EXEC) {
47674781
char *end_of_arg = (char *)(arg + item->arg_len);
47684782
int saved = *end_of_arg;
@@ -4810,22 +4824,19 @@ static int pick_commits(struct repository *r,
48104824
get_item_line_length(todo_list,
48114825
todo_list->current),
48124826
get_item_line(todo_list, todo_list->current));
4813-
todo_list->current--;
4814-
if (save_todo(todo_list, opts))
4827+
if (save_todo(todo_list, opts, reschedule))
48154828
return -1;
48164829
if (item->commit)
4817-
return error_with_patch(r,
4818-
item->commit,
4819-
arg, item->arg_len,
4820-
opts, res, 0);
4830+
write_rebase_head(&item->commit->object.oid);
48214831
} else if (is_rebase_i(opts) && check_todo && !res &&
48224832
reread_todo_if_changed(r, todo_list, opts)) {
48234833
return -1;
48244834
}
48254835

4826-
todo_list->current++;
48274836
if (res)
48284837
return res;
4838+
4839+
todo_list->current++;
48294840
}
48304841

48314842
if (is_rebase_i(opts)) {
@@ -4978,6 +4989,11 @@ static int commit_staged_changes(struct repository *r,
49784989

49794990
is_clean = !has_uncommitted_changes(r, 0);
49804991

4992+
if (!is_clean && !file_exists(rebase_path_message())) {
4993+
const char *gpg_opt = gpg_sign_opt_quoted(opts);
4994+
4995+
return error(_(staged_changes_advice), gpg_opt, gpg_opt);
4996+
}
49814997
if (file_exists(rebase_path_amend())) {
49824998
struct strbuf rev = STRBUF_INIT;
49834999
struct object_id head, to_amend;

t/t3404-rebase-interactive.sh

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,8 @@ test_expect_success 'clean error after failed "exec"' '
604604
echo "edited again" > file7 &&
605605
git add file7 &&
606606
test_must_fail git rebase --continue 2>error &&
607-
test_i18ngrep "you have staged changes in your working tree" error
607+
test_i18ngrep "you have staged changes in your working tree" error &&
608+
test_i18ngrep ! "could not open.*for reading" error
608609
'
609610

610611
test_expect_success 'rebase a detached HEAD' '
@@ -1276,20 +1277,34 @@ test_expect_success 'todo count' '
12761277
'
12771278

12781279
test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '
1279-
git checkout --force branch2 &&
1280+
git checkout --force A &&
12801281
git clean -f &&
1282+
cat >todo <<-EOF &&
1283+
exec >file2
1284+
pick $(git rev-parse B) B
1285+
pick $(git rev-parse C) C
1286+
pick $(git rev-parse D) D
1287+
exec cat .git/rebase-merge/done >actual
1288+
EOF
12811289
(
1282-
set_fake_editor &&
1283-
FAKE_LINES="edit 1 2" git rebase -i A
1284-
) &&
1285-
test_cmp_rev HEAD F &&
1286-
test_path_is_missing file6 &&
1287-
>file6 &&
1288-
test_must_fail git rebase --continue &&
1289-
test_cmp_rev HEAD F &&
1290-
rm file6 &&
1290+
set_replace_editor todo &&
1291+
test_must_fail git rebase -i A
1292+
) &&
1293+
test_cmp_rev HEAD B &&
1294+
test_cmp_rev REBASE_HEAD C &&
1295+
head -n3 todo >expect &&
1296+
test_cmp expect .git/rebase-merge/done &&
1297+
rm file2 &&
1298+
test_path_is_missing .git/rebase-merge/patch &&
1299+
echo changed >file1 &&
1300+
git add file1 &&
1301+
test_must_fail git rebase --continue 2>err &&
1302+
grep "error: you have staged changes in your working tree" err &&
1303+
git reset --hard HEAD &&
12911304
git rebase --continue &&
1292-
test_cmp_rev HEAD I
1305+
test_cmp_rev HEAD D &&
1306+
tail -n3 todo >>expect &&
1307+
test_cmp expect actual
12931308
'
12941309

12951310
test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '
@@ -1305,7 +1320,14 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'
13051320
>file6 &&
13061321
test_must_fail git rebase --continue &&
13071322
test_cmp_rev HEAD F &&
1323+
test_cmp_rev REBASE_HEAD I &&
13081324
rm file6 &&
1325+
test_path_is_missing .git/rebase-merge/patch &&
1326+
echo changed >file1 &&
1327+
git add file1 &&
1328+
test_must_fail git rebase --continue 2>err &&
1329+
grep "error: you have staged changes in your working tree" err &&
1330+
git reset --hard HEAD &&
13091331
git rebase --continue &&
13101332
test $(git cat-file commit HEAD | sed -ne \$p) = I &&
13111333
git reset --hard original-branch2
@@ -1323,7 +1345,14 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '
13231345
>file6 &&
13241346
test_must_fail git rebase --continue &&
13251347
test $(git cat-file commit HEAD | sed -ne \$p) = F &&
1348+
test_cmp_rev REBASE_HEAD I &&
13261349
rm file6 &&
1350+
test_path_is_missing .git/rebase-merge/patch &&
1351+
echo changed >file1 &&
1352+
git add file1 &&
1353+
test_must_fail git rebase --continue 2>err &&
1354+
grep "error: you have staged changes in your working tree" err &&
1355+
git reset --hard HEAD &&
13271356
git rebase --continue &&
13281357
test $(git cat-file commit HEAD | sed -ne \$p) = I
13291358
'

0 commit comments

Comments
 (0)