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>
This commit is contained in:
Jonathan Nieder 2011-12-10 06:49:25 -06:00 committed by Junio C Hamano
parent 1df9bf46d6
commit 093a309136
2 changed files with 156 additions and 6 deletions

View file

@ -1038,18 +1038,35 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
return 0;
}
static int continue_single_pick(void)
{
const char *argv[] = { "commit", NULL };
if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
!file_exists(git_path("REVERT_HEAD")))
return error(_("no cherry-pick or revert in progress"));
return run_command_v_opt(argv, RUN_GIT_CMD);
}
static int sequencer_continue(struct replay_opts *opts)
{
struct commit_list *todo_list = NULL;
if (!file_exists(git_path(SEQ_TODO_FILE)))
return error(_("No %s in progress"), action_name(opts));
return continue_single_pick();
read_populate_opts(&opts);
read_populate_todo(&todo_list, opts);
/* Verify that the conflict has been resolved */
if (!index_differs_from("HEAD", 0))
todo_list = todo_list->next;
if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
file_exists(git_path("REVERT_HEAD"))) {
int ret = continue_single_pick();
if (ret)
return ret;
}
if (index_differs_from("HEAD", 0))
return error_dirty_index(opts);
todo_list = todo_list->next;
return pick_commits(todo_list, opts);
}

View file

@ -2,6 +2,7 @@
test_description='Test cherry-pick continuation features
+ conflicting: rewrites unrelated to conflicting
+ yetanotherpick: rewrites foo to e
+ anotherpick: rewrites foo to d
+ picked: rewrites foo to c
@ -27,6 +28,7 @@ test_cmp_rev () {
}
test_expect_success setup '
git config advice.detachedhead false
echo unrelated >unrelated &&
git add unrelated &&
test_commit initial foo a &&
@ -35,8 +37,8 @@ test_expect_success setup '
test_commit picked foo c &&
test_commit anotherpick foo d &&
test_commit yetanotherpick foo e &&
git config advice.detachedhead false
pristine_detach initial &&
test_commit conflicting unrelated
'
test_expect_success 'cherry-pick persists data on failure' '
@ -243,7 +245,66 @@ test_expect_success '--continue complains when there are unresolved conflicts' '
test_must_fail git cherry-pick --continue
'
test_expect_success '--continue continues after conflicts are resolved' '
test_expect_success '--continue of single cherry-pick' '
pristine_detach initial &&
echo c >expect &&
test_must_fail git cherry-pick picked &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
test_cmp expect foo &&
test_cmp_rev initial HEAD^ &&
git diff --exit-code HEAD &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success '--continue of single revert' '
pristine_detach initial &&
echo resolved >expect &&
echo "Revert \"picked\"" >expect.msg &&
test_must_fail git revert picked &&
echo resolved >foo &&
git add foo &&
git cherry-pick --continue &&
git diff --exit-code HEAD &&
test_cmp expect foo &&
test_cmp_rev initial HEAD^ &&
git diff-tree -s --pretty=tformat:%s HEAD >msg &&
test_cmp expect.msg msg &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
test_must_fail git rev-parse --verify REVERT_HEAD
'
test_expect_success '--continue after resolving conflicts' '
pristine_detach initial &&
echo d >expect &&
cat >expect.log <<-\EOF &&
OBJID
:100644 100644 OBJID OBJID M foo
OBJID
:100644 100644 OBJID OBJID M foo
OBJID
:100644 100644 OBJID OBJID M unrelated
OBJID
:000000 100644 OBJID OBJID A foo
:000000 100644 OBJID OBJID A unrelated
EOF
test_must_fail git cherry-pick base..anotherpick &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
{
git rev-list HEAD |
git diff-tree --root --stdin |
sed "s/$_x40/OBJID/g"
} >actual.log &&
test_cmp expect foo &&
test_cmp expect.log actual.log
'
test_expect_success '--continue after resolving conflicts and committing' '
pristine_detach initial &&
test_must_fail git cherry-pick base..anotherpick &&
echo "c" >foo &&
@ -270,6 +331,29 @@ test_expect_success '--continue continues after conflicts are resolved' '
test_cmp expect actual
'
test_expect_success '--continue asks for help after resolving patch to nil' '
pristine_detach conflicting &&
test_must_fail git cherry-pick initial..picked &&
test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
git checkout HEAD -- unrelated &&
test_must_fail git cherry-pick --continue 2>msg &&
test_i18ngrep "The previous cherry-pick is now empty" msg
'
test_expect_failure 'follow advice and skip nil patch' '
pristine_detach conflicting &&
test_must_fail git cherry-pick initial..picked &&
git checkout HEAD -- unrelated &&
test_must_fail git cherry-pick --continue &&
git reset &&
git cherry-pick --continue &&
git rev-list initial..HEAD >commits &&
test_line_count = 3 commits
'
test_expect_success '--continue respects opts' '
pristine_detach initial &&
test_must_fail git cherry-pick -x base..anotherpick &&
@ -288,6 +372,29 @@ test_expect_success '--continue respects opts' '
grep "cherry picked from" anotherpick_msg
'
test_expect_success '--continue of single-pick respects -x' '
pristine_detach initial &&
test_must_fail git cherry-pick -x picked &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
test_path_is_missing .git/sequencer &&
git cat-file commit HEAD >msg &&
grep "cherry picked from" msg
'
test_expect_success '--continue respects -x in first commit in multi-pick' '
pristine_detach initial &&
test_must_fail git cherry-pick -x picked anotherpick &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
test_path_is_missing .git/sequencer &&
git cat-file commit HEAD^ >msg &&
picked=$(git rev-parse --verify picked) &&
grep "cherry picked from.*$picked" msg
'
test_expect_success '--signoff is not automatically propagated to resolved conflict' '
pristine_detach initial &&
test_must_fail git cherry-pick --signoff base..anotherpick &&
@ -306,6 +413,32 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
grep "Signed-off-by:" anotherpick_msg
'
test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
pristine_detach initial &&
test_must_fail git cherry-pick -s picked anotherpick &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
git diff --exit-code HEAD &&
test_cmp_rev initial HEAD^^ &&
git cat-file commit HEAD^ >msg &&
! grep Signed-off-by: msg
'
test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
pristine_detach initial &&
test_must_fail git cherry-pick -s picked &&
echo c >foo &&
git add foo &&
git cherry-pick --continue &&
git diff --exit-code HEAD &&
test_cmp_rev initial HEAD^ &&
git cat-file commit HEAD >msg &&
! grep Signed-off-by: msg
'
test_expect_success 'malformed instruction sheet 1' '
pristine_detach initial &&
test_must_fail git cherry-pick base..anotherpick &&