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>

Jonathan Nieder committed Dec 10, 2011 at 06:49 UTC 093a309136c38eca0ea2dd5da3c68b483443d113
2 files changed +156 -6
builtin/revert.c
+20 -3
@@ -1038,18 +1038,35 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
1038 return 0;
1039 }
1040
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 +
1051 static int sequencer_continue(struct replay_opts *opts)
1052 {
1053 struct commit_list *todo_list = NULL;
1054
1055 if (!file_exists(git_path(SEQ_TODO_FILE)))
1046 - return error(_("No %s in progress"), action_name(opts));
1056 + return continue_single_pick();
1057 read_populate_opts(&opts);
1058 read_populate_todo(&todo_list, opts);
1059
1060 /* 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;
1070 return pick_commits(todo_list, opts);
1071 }
1072
t/t3510-cherry-pick-sequence.sh
+136 -3
@@ -2,6 +2,7 @@
2
3 test_description='Test cherry-pick continuation features
4
5 + + conflicting: rewrites unrelated to conflicting
6 + yetanotherpick: rewrites foo to e
7 + anotherpick: rewrites foo to d
8 + picked: rewrites foo to c
@@ -27,6 +28,7 @@ test_cmp_rev () {
28 }
29
30 test_expect_success setup '
31 + git config advice.detachedhead false
32 echo unrelated >unrelated &&
33 git add unrelated &&
34 test_commit initial foo a &&
@@ -35,8 +37,8 @@ test_expect_success setup '
37 test_commit picked foo c &&
38 test_commit anotherpick foo d &&
39 test_commit yetanotherpick foo e &&
38 - git config advice.detachedhead false
39 -
40 + pristine_detach initial &&
41 + test_commit conflicting unrelated
42 '
43
44 test_expect_success 'cherry-pick persists data on failure' '
@@ -243,7 +245,66 @@ test_expect_success '--continue complains when there are unresolved conflicts' '
245 test_must_fail git cherry-pick --continue
246 '
247
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' '
308 pristine_detach initial &&
309 test_must_fail git cherry-pick base..anotherpick &&
310 echo "c" >foo &&
@@ -270,6 +331,29 @@ test_expect_success '--continue continues after conflicts are resolved' '
331 test_cmp expect actual
332 '
333
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 +
357 test_expect_success '--continue respects opts' '
358 pristine_detach initial &&
359 test_must_fail git cherry-pick -x base..anotherpick &&
@@ -288,6 +372,29 @@ test_expect_success '--continue respects opts' '
372 grep "cherry picked from" anotherpick_msg
373 '
374
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 +
398 test_expect_success '--signoff is not automatically propagated to resolved conflict' '
399 pristine_detach initial &&
400 test_must_fail git cherry-pick --signoff base..anotherpick &&
@@ -306,6 +413,32 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
413 grep "Signed-off-by:" anotherpick_msg
414 '
415
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 +
442 test_expect_success 'malformed instruction sheet 1' '
443 pristine_detach initial &&
444 test_must_fail git cherry-pick base..anotherpick &&