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:
parent
2161da1039
commit
d7e5c0cbfb
@ -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
|
introduces, recording a new commit for each. This requires your
|
||||||
working tree to be clean (no modifications from the HEAD commit).
|
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
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
<commit>...::
|
<commit>...::
|
||||||
|
@ -25,7 +25,8 @@ blobs contained in a commit.
|
|||||||
first match in the following rules:
|
first match in the following rules:
|
||||||
|
|
||||||
. if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
|
. 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;
|
. 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.
|
them easily.
|
||||||
MERGE_HEAD records the commit(s) you are merging into your branch
|
MERGE_HEAD records the commit(s) you are merging into your branch
|
||||||
when you run 'git merge'.
|
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
|
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.
|
the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
|
||||||
|
1
branch.c
1
branch.c
@ -217,6 +217,7 @@ void create_branch(const char *head,
|
|||||||
|
|
||||||
void remove_branch_state(void)
|
void remove_branch_state(void)
|
||||||
{
|
{
|
||||||
|
unlink(git_path("CHERRY_PICK_HEAD"));
|
||||||
unlink(git_path("MERGE_HEAD"));
|
unlink(git_path("MERGE_HEAD"));
|
||||||
unlink(git_path("MERGE_RR"));
|
unlink(git_path("MERGE_RR"));
|
||||||
unlink(git_path("MERGE_MSG"));
|
unlink(git_path("MERGE_MSG"));
|
||||||
|
@ -1424,6 +1424,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
|||||||
die("cannot update HEAD ref");
|
die("cannot update HEAD ref");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unlink(git_path("CHERRY_PICK_HEAD"));
|
||||||
unlink(git_path("MERGE_HEAD"));
|
unlink(git_path("MERGE_HEAD"));
|
||||||
unlink(git_path("MERGE_MSG"));
|
unlink(git_path("MERGE_MSG"));
|
||||||
unlink(git_path("MERGE_MODE"));
|
unlink(git_path("MERGE_MODE"));
|
||||||
|
@ -969,6 +969,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||||||
else
|
else
|
||||||
die("You have not concluded your merge (MERGE_HEAD exists).");
|
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();
|
resolve_undo_clear();
|
||||||
|
|
||||||
if (verbosity < 0)
|
if (verbosity < 0)
|
||||||
|
@ -231,6 +231,22 @@ static void set_author_ident_env(const char *message)
|
|||||||
sha1_to_hex(commit->object.sha1));
|
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, ...)
|
static void advise(const char *advice, ...)
|
||||||
{
|
{
|
||||||
va_list params;
|
va_list params;
|
||||||
@ -246,6 +262,12 @@ static void print_advice(void)
|
|||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
fprintf(stderr, "%s\n", 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,8 +275,7 @@ static void print_advice(void)
|
|||||||
advise("with 'git add <paths>' or 'git rm <paths>'");
|
advise("with 'git add <paths>' or 'git rm <paths>'");
|
||||||
|
|
||||||
if (action == CHERRY_PICK)
|
if (action == CHERRY_PICK)
|
||||||
advise("and commit the result with 'git commit -c %s'",
|
advise("and commit the result with 'git commit -c CHERRY_PICK_HEAD'");
|
||||||
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void write_message(struct strbuf *msgbuf, const char *filename)
|
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, sha1_to_hex(commit->object.sha1));
|
||||||
strbuf_addstr(&msgbuf, ")\n");
|
strbuf_addstr(&msgbuf, ")\n");
|
||||||
}
|
}
|
||||||
|
if (!no_commit)
|
||||||
|
write_cherry_pick_head();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
|
if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
|
||||||
|
@ -52,13 +52,87 @@ test_expect_success 'advice from failed cherry-pick' "
|
|||||||
error: could not apply \$picked... picked
|
error: could not apply \$picked... picked
|
||||||
hint: after resolving the conflicts, mark the corrected paths
|
hint: after resolving the conflicts, mark the corrected paths
|
||||||
hint: with 'git add <paths>' or 'git rm <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
|
EOF
|
||||||
test_must_fail git cherry-pick picked 2>actual &&
|
test_must_fail git cherry-pick picked 2>actual &&
|
||||||
|
|
||||||
test_cmp expected 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' '
|
test_expect_success 'failed cherry-pick produces dirty index' '
|
||||||
pristine_detach initial &&
|
pristine_detach initial &&
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user