Introduce CHERRY_PICK_HEAD

When a cherry-pick conflicts git advises:

 $ git commit -c <original commit id>

to preserve the original commit message and authorship. Instead, let's
record the original commit id in CHERRY_PICK_HEAD and advise:

  $ git commit -c CHERRY_PICK_HEAD

A later patch teaches git to handle the '-c CHERRY_PICK_HEAD' part.
Note that we record CHERRY_PICK_HEAD even in the case where there
are no conflicts so that we may use it to communicate authorship to
commit; this will then allow us to remove set_author_ident_env from
revert.c. However, we do not record CHERRY_PICK_HEAD when --no-commit
is used, as presumably the user intends to further edit the commit
and possibly even cherry-pick additional commits on top.

Tests and documentation contributed by Jonathan Nieder.

Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jay Soffian 2011-02-19 23:12:27 -05:00 committed by Junio C Hamano
parent 2161da1039
commit d7e5c0cbfb
7 changed files with 132 additions and 4 deletions

View File

@ -16,6 +16,25 @@ Given one or more existing commits, apply the change each one
introduces, recording a new commit for each. This requires your
working tree to be clean (no modifications from the HEAD commit).
When it is not obvious how to apply a change, the following
happens:
1. The current branch and `HEAD` pointer stay at the last commit
successfully made.
2. The `CHERRY_PICK_HEAD` ref is set to point at the commit that
introduced the change that is difficult to apply.
3. Paths in which the change applied cleanly are updated both
in the index file and in your working tree.
4. For conflicting paths, the index file records up to three
versions, as described in the "TRUE MERGE" section of
linkgit:git-merge[1]. The working tree files will include
a description of the conflict bracketed by the usual
conflict markers `<<<<<<<` and `>>>>>>>`.
5. No other modifications are made.
See linkgit:git-merge[1] for some hints on resolving such
conflicts.
OPTIONS
-------
<commit>...::

View File

@ -25,7 +25,8 @@ blobs contained in a commit.
first match in the following rules:
. if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`);
useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD`, `MERGE_HEAD`
and `CHERRY_PICK_HEAD`);
. otherwise, `refs/<name>` if exists;
@ -46,6 +47,8 @@ you can change the tip of the branch back to the state before you ran
them easily.
MERGE_HEAD records the commit(s) you are merging into your branch
when you run 'git merge'.
CHERRY_PICK_HEAD records the commit you are cherry-picking
when you run 'git cherry-pick'.
+
Note that any of the `refs/*` cases above may come either from
the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.

View File

@ -217,6 +217,7 @@ void create_branch(const char *head,
void remove_branch_state(void)
{
unlink(git_path("CHERRY_PICK_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_RR"));
unlink(git_path("MERGE_MSG"));

View File

@ -1424,6 +1424,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die("cannot update HEAD ref");
}
unlink(git_path("CHERRY_PICK_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("MERGE_MODE"));

View File

@ -969,6 +969,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
die("You have not concluded your merge (MERGE_HEAD exists).");
}
if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
if (advice_resolve_conflict)
die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you can merge.");
else
die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).");
}
resolve_undo_clear();
if (verbosity < 0)

View File

@ -231,6 +231,22 @@ static void set_author_ident_env(const char *message)
sha1_to_hex(commit->object.sha1));
}
static void write_cherry_pick_head(void)
{
int fd;
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
if (fd < 0)
die_errno("Could not open '%s' for writing",
git_path("CHERRY_PICK_HEAD"));
if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
die_errno("Could not write to '%s'", git_path("CHERRY_PICK_HEAD"));
strbuf_release(&buf);
}
static void advise(const char *advice, ...)
{
va_list params;
@ -246,6 +262,12 @@ static void print_advice(void)
if (msg) {
fprintf(stderr, "%s\n", msg);
/*
* A conflict has occured but the porcelain
* (typically rebase --interactive) wants to take care
* of the commit itself so remove CHERRY_PICK_HEAD
*/
unlink(git_path("CHERRY_PICK_HEAD"));
return;
}
@ -253,8 +275,7 @@ static void print_advice(void)
advise("with 'git add <paths>' or 'git rm <paths>'");
if (action == CHERRY_PICK)
advise("and commit the result with 'git commit -c %s'",
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
advise("and commit the result with 'git commit -c CHERRY_PICK_HEAD'");
}
static void write_message(struct strbuf *msgbuf, const char *filename)
@ -489,6 +510,8 @@ static int do_pick_commit(void)
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
strbuf_addstr(&msgbuf, ")\n");
}
if (!no_commit)
write_cherry_pick_head();
}
if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {

View File

@ -52,13 +52,87 @@ test_expect_success 'advice from failed cherry-pick' "
error: could not apply \$picked... picked
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 -c \$picked'
hint: and commit the result with 'git commit -c CHERRY_PICK_HEAD'
EOF
test_must_fail git cherry-pick picked 2>actual &&
test_cmp expected actual
"
test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
test_cmp_rev picked CHERRY_PICK_HEAD
'
test_expect_success 'successful cherry-pick does not set CHERRY_PICK_HEAD' '
pristine_detach initial &&
git cherry-pick base &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' '
pristine_detach initial &&
git cherry-pick --no-commit base &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' '
pristine_detach initial &&
(
GIT_CHERRY_PICK_HELP="and then do something else" &&
export GIT_CHERRY_PICK_HELP &&
test_must_fail git cherry-pick picked
) &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success 'git reset clears CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
git reset &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success 'failed commit does not clear CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
test_must_fail git commit &&
test_cmp_rev picked CHERRY_PICK_HEAD
'
test_expect_success 'cancelled commit does not clear CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
echo resolved >foo &&
git add foo &&
git update-index --refresh -q &&
test_must_fail git diff-index --exit-code HEAD &&
(
GIT_EDITOR=false &&
export GIT_EDITOR &&
test_must_fail git commit
) &&
test_cmp_rev picked CHERRY_PICK_HEAD
'
test_expect_success 'successful commit clears CHERRY_PICK_HEAD' '
pristine_detach initial &&
test_must_fail git cherry-pick picked &&
echo resolved >foo &&
git add foo &&
git commit &&
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
'
test_expect_success 'failed cherry-pick produces dirty index' '
pristine_detach initial &&