Skip to content

Commit 093a309

Browse files
jrngitster
authored andcommitted
revert: allow cherry-pick --continue to commit before resuming
When "git cherry-pick ..bar" encounters conflicts, permit the operator to use cherry-pick --continue after resolving them as a shortcut for "git commit && git cherry-pick --continue" to record the resolution and carry on with the rest of the sequence. This improves the analogy with "git rebase" (in olden days --continue was the way to preserve authorship when a rebase encountered conflicts) and fits well with a general UI goal of making "git cmd --continue" save humans the trouble of deciding what to do next. Example: after encountering a conflict from running "git cherry-pick foo bar baz": CONFLICT (content): Merge conflict in main.c error: could not apply f78a8d98c... bar! hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' hint: and commit the result with 'git commit' We edit main.c to resolve the conflict, mark it acceptable with "git add main.c", and can run "cherry-pick --continue" to resume the sequence. $ git cherry-pick --continue [editor opens to confirm commit message] [master 78c8a8c98] bar! 1 files changed, 1 insertions(+), 1 deletions(-) [master 87ca8798c] baz! 1 files changed, 1 insertions(+), 1 deletions(-) This is done for both codepaths to pick multiple commits and a single commit. Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 1df9bf4 commit 093a309

2 files changed

Lines changed: 156 additions & 6 deletions

File tree

builtin/revert.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,18 +1038,35 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
10381038
return 0;
10391039
}
10401040

1041+
static int continue_single_pick(void)
1042+
{
1043+
const char *argv[] = { "commit", NULL };
1044+
1045+
if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
1046+
!file_exists(git_path("REVERT_HEAD")))
1047+
return error(_("no cherry-pick or revert in progress"));
1048+
return run_command_v_opt(argv, RUN_GIT_CMD);
1049+
}
1050+
10411051
static int sequencer_continue(struct replay_opts *opts)
10421052
{
10431053
struct commit_list *todo_list = NULL;
10441054

10451055
if (!file_exists(git_path(SEQ_TODO_FILE)))
1046-
return error(_("No %s in progress"), action_name(opts));
1056+
return continue_single_pick();
10471057
read_populate_opts(&opts);
10481058
read_populate_todo(&todo_list, opts);
10491059

10501060
/* Verify that the conflict has been resolved */
1051-
if (!index_differs_from("HEAD", 0))
1052-
todo_list = todo_list->next;
1061+
if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
1062+
file_exists(git_path("REVERT_HEAD"))) {
1063+
int ret = continue_single_pick();
1064+
if (ret)
1065+
return ret;
1066+
}
1067+
if (index_differs_from("HEAD", 0))
1068+
return error_dirty_index(opts);
1069+
todo_list = todo_list->next;
10531070
return pick_commits(todo_list, opts);
10541071
}
10551072

t/t3510-cherry-pick-sequence.sh

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
test_description='Test cherry-pick continuation features
44
5+
+ conflicting: rewrites unrelated to conflicting
56
+ yetanotherpick: rewrites foo to e
67
+ anotherpick: rewrites foo to d
78
+ picked: rewrites foo to c
@@ -27,6 +28,7 @@ test_cmp_rev () {
2728
}
2829

2930
test_expect_success setup '
31+
git config advice.detachedhead false
3032
echo unrelated >unrelated &&
3133
git add unrelated &&
3234
test_commit initial foo a &&
@@ -35,8 +37,8 @@ test_expect_success setup '
3537
test_commit picked foo c &&
3638
test_commit anotherpick foo d &&
3739
test_commit yetanotherpick foo e &&
38-
git config advice.detachedhead false
39-
40+
pristine_detach initial &&
41+
test_commit conflicting unrelated
4042
'
4143

4244
test_expect_success 'cherry-pick persists data on failure' '
@@ -243,7 +245,66 @@ test_expect_success '--continue complains when there are unresolved conflicts' '
243245
test_must_fail git cherry-pick --continue
244246
'
245247

246-
test_expect_success '--continue continues after conflicts are resolved' '
248+
test_expect_success '--continue of single cherry-pick' '
249+
pristine_detach initial &&
250+
echo c >expect &&
251+
test_must_fail git cherry-pick picked &&
252+
echo c >foo &&
253+
git add foo &&
254+
git cherry-pick --continue &&
255+
256+
test_cmp expect foo &&
257+
test_cmp_rev initial HEAD^ &&
258+
git diff --exit-code HEAD &&
259+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
260+
'
261+
262+
test_expect_success '--continue of single revert' '
263+
pristine_detach initial &&
264+
echo resolved >expect &&
265+
echo "Revert \"picked\"" >expect.msg &&
266+
test_must_fail git revert picked &&
267+
echo resolved >foo &&
268+
git add foo &&
269+
git cherry-pick --continue &&
270+
271+
git diff --exit-code HEAD &&
272+
test_cmp expect foo &&
273+
test_cmp_rev initial HEAD^ &&
274+
git diff-tree -s --pretty=tformat:%s HEAD >msg &&
275+
test_cmp expect.msg msg &&
276+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
277+
test_must_fail git rev-parse --verify REVERT_HEAD
278+
'
279+
280+
test_expect_success '--continue after resolving conflicts' '
281+
pristine_detach initial &&
282+
echo d >expect &&
283+
cat >expect.log <<-\EOF &&
284+
OBJID
285+
:100644 100644 OBJID OBJID M foo
286+
OBJID
287+
:100644 100644 OBJID OBJID M foo
288+
OBJID
289+
:100644 100644 OBJID OBJID M unrelated
290+
OBJID
291+
:000000 100644 OBJID OBJID A foo
292+
:000000 100644 OBJID OBJID A unrelated
293+
EOF
294+
test_must_fail git cherry-pick base..anotherpick &&
295+
echo c >foo &&
296+
git add foo &&
297+
git cherry-pick --continue &&
298+
{
299+
git rev-list HEAD |
300+
git diff-tree --root --stdin |
301+
sed "s/$_x40/OBJID/g"
302+
} >actual.log &&
303+
test_cmp expect foo &&
304+
test_cmp expect.log actual.log
305+
'
306+
307+
test_expect_success '--continue after resolving conflicts and committing' '
247308
pristine_detach initial &&
248309
test_must_fail git cherry-pick base..anotherpick &&
249310
echo "c" >foo &&
@@ -270,6 +331,29 @@ test_expect_success '--continue continues after conflicts are resolved' '
270331
test_cmp expect actual
271332
'
272333

334+
test_expect_success '--continue asks for help after resolving patch to nil' '
335+
pristine_detach conflicting &&
336+
test_must_fail git cherry-pick initial..picked &&
337+
338+
test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
339+
git checkout HEAD -- unrelated &&
340+
test_must_fail git cherry-pick --continue 2>msg &&
341+
test_i18ngrep "The previous cherry-pick is now empty" msg
342+
'
343+
344+
test_expect_failure 'follow advice and skip nil patch' '
345+
pristine_detach conflicting &&
346+
test_must_fail git cherry-pick initial..picked &&
347+
348+
git checkout HEAD -- unrelated &&
349+
test_must_fail git cherry-pick --continue &&
350+
git reset &&
351+
git cherry-pick --continue &&
352+
353+
git rev-list initial..HEAD >commits &&
354+
test_line_count = 3 commits
355+
'
356+
273357
test_expect_success '--continue respects opts' '
274358
pristine_detach initial &&
275359
test_must_fail git cherry-pick -x base..anotherpick &&
@@ -288,6 +372,29 @@ test_expect_success '--continue respects opts' '
288372
grep "cherry picked from" anotherpick_msg
289373
'
290374

375+
test_expect_success '--continue of single-pick respects -x' '
376+
pristine_detach initial &&
377+
test_must_fail git cherry-pick -x picked &&
378+
echo c >foo &&
379+
git add foo &&
380+
git cherry-pick --continue &&
381+
test_path_is_missing .git/sequencer &&
382+
git cat-file commit HEAD >msg &&
383+
grep "cherry picked from" msg
384+
'
385+
386+
test_expect_success '--continue respects -x in first commit in multi-pick' '
387+
pristine_detach initial &&
388+
test_must_fail git cherry-pick -x picked anotherpick &&
389+
echo c >foo &&
390+
git add foo &&
391+
git cherry-pick --continue &&
392+
test_path_is_missing .git/sequencer &&
393+
git cat-file commit HEAD^ >msg &&
394+
picked=$(git rev-parse --verify picked) &&
395+
grep "cherry picked from.*$picked" msg
396+
'
397+
291398
test_expect_success '--signoff is not automatically propagated to resolved conflict' '
292399
pristine_detach initial &&
293400
test_must_fail git cherry-pick --signoff base..anotherpick &&
@@ -306,6 +413,32 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
306413
grep "Signed-off-by:" anotherpick_msg
307414
'
308415

416+
test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
417+
pristine_detach initial &&
418+
test_must_fail git cherry-pick -s picked anotherpick &&
419+
echo c >foo &&
420+
git add foo &&
421+
git cherry-pick --continue &&
422+
423+
git diff --exit-code HEAD &&
424+
test_cmp_rev initial HEAD^^ &&
425+
git cat-file commit HEAD^ >msg &&
426+
! grep Signed-off-by: msg
427+
'
428+
429+
test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
430+
pristine_detach initial &&
431+
test_must_fail git cherry-pick -s picked &&
432+
echo c >foo &&
433+
git add foo &&
434+
git cherry-pick --continue &&
435+
436+
git diff --exit-code HEAD &&
437+
test_cmp_rev initial HEAD^ &&
438+
git cat-file commit HEAD >msg &&
439+
! grep Signed-off-by: msg
440+
'
441+
309442
test_expect_success 'malformed instruction sheet 1' '
310443
pristine_detach initial &&
311444
test_must_fail git cherry-pick base..anotherpick &&

0 commit comments

Comments
 (0)