Skip to content

Commit

Permalink
Introduce CHERRY_PICK_HEAD
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
jaysoffian authored and gitster committed Feb 22, 2011
1 parent 2161da1 commit d7e5c0c
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 4 deletions.
19 changes: 19 additions & 0 deletions Documentation/git-cherry-pick.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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>...::
Expand Down
5 changes: 4 additions & 1 deletion Documentation/revisions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
1 change: 1 addition & 0 deletions builtin/commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
7 changes: 7 additions & 0 deletions builtin/merge.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 25 additions & 2 deletions builtin/revert.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -246,15 +262,20 @@ 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;
}

advise("after resolving the conflicts, mark the corrected paths");
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)
Expand Down Expand Up @@ -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) {
Expand Down
76 changes: 75 additions & 1 deletion t/t3507-cherry-pick-conflict.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down

0 comments on commit d7e5c0c

Please sign in to comment.