Merge branch 'js/cherry-pick-usability'

* js/cherry-pick-usability:
  Teach commit about CHERRY_PICK_HEAD
  bash: teach __git_ps1 about CHERRY_PICK_HEAD
  Introduce CHERRY_PICK_HEAD
  t3507: introduce pristine-detach helper
This commit is contained in:
Junio C Hamano 2011-03-09 15:56:17 -08:00
commit 66ecd2d053
12 changed files with 307 additions and 155 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 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>...::

View file

@ -84,9 +84,10 @@ OPTIONS
linkgit:git-rebase[1] for details. linkgit:git-rebase[1] for details.
--reset-author:: --reset-author::
When used with -C/-c/--amend options, declare that the When used with -C/-c/--amend options, or when committing after a
authorship of the resulting commit now belongs of the committer. a conflicting cherry-pick, declare that the authorship of the
This also renews the author timestamp. resulting commit now belongs of the committer. This also renews
the author timestamp.
--short:: --short::
When doing a dry-run, give the output in the short-format. See When doing a dry-run, give the output in the short-format. See

View file

@ -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.

View file

@ -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"));

View file

@ -54,9 +54,17 @@ static const char empty_amend_advice[] =
"it empty. You can repeat your command with --allow-empty, or you can\n" "it empty. You can repeat your command with --allow-empty, or you can\n"
"remove the commit entirely with \"git reset HEAD^\".\n"; "remove the commit entirely with \"git reset HEAD^\".\n";
static const char empty_cherry_pick_advice[] =
"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
"If you wish to commit it anyway, use:\n"
"\n"
" git commit --allow-empty\n"
"\n"
"Otherwise, please use 'git reset'\n";
static unsigned char head_sha1[20]; static unsigned char head_sha1[20];
static char *use_message_buffer; static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG"; static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */ static struct lock_file index_lock; /* real index */
static struct lock_file false_lock; /* used only for partial commits */ static struct lock_file false_lock; /* used only for partial commits */
@ -68,6 +76,11 @@ static enum {
static const char *logfile, *force_author; static const char *logfile, *force_author;
static const char *template_file; static const char *template_file;
/*
* The _message variables are commit names from which to take
* the commit message and/or authorship.
*/
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message; static char *edit_message, *use_message;
static char *fixup_message, *squash_message; static char *fixup_message, *squash_message;
static int all, edit_flag, also, interactive, only, amend, signoff; static int all, edit_flag, also, interactive, only, amend, signoff;
@ -88,7 +101,8 @@ static enum {
} cleanup_mode; } cleanup_mode;
static char *cleanup_arg; static char *cleanup_arg;
static int use_editor = 1, initial_commit, in_merge, include_status = 1; static enum commit_whence whence;
static int use_editor = 1, initial_commit, include_status = 1;
static int show_ignored_in_status; static int show_ignored_in_status;
static const char *only_include_assumed; static const char *only_include_assumed;
static struct strbuf message; static struct strbuf message;
@ -163,6 +177,36 @@ static struct option builtin_commit_options[] = {
OPT_END() OPT_END()
}; };
static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path("MERGE_HEAD")))
whence = FROM_MERGE;
else if (file_exists(git_path("CHERRY_PICK_HEAD")))
whence = FROM_CHERRY_PICK;
else
whence = FROM_COMMIT;
if (s)
s->whence = whence;
}
static const char *whence_s(void)
{
char *s = "";
switch (whence) {
case FROM_COMMIT:
break;
case FROM_MERGE:
s = "merge";
break;
case FROM_CHERRY_PICK:
s = "cherry-pick";
break;
}
return s;
}
static void rollback_index_files(void) static void rollback_index_files(void)
{ {
switch (commit_style) { switch (commit_style) {
@ -378,8 +422,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/ */
commit_style = COMMIT_PARTIAL; commit_style = COMMIT_PARTIAL;
if (in_merge) if (whence != FROM_COMMIT)
die("cannot do a partial commit during a merge."); die("cannot do a partial commit during a %s.", whence_s());
memset(&partial, 0, sizeof(partial)); memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1; partial.strdup_strings = 1;
@ -469,18 +513,18 @@ static void determine_author_info(struct strbuf *author_ident)
email = getenv("GIT_AUTHOR_EMAIL"); email = getenv("GIT_AUTHOR_EMAIL");
date = getenv("GIT_AUTHOR_DATE"); date = getenv("GIT_AUTHOR_DATE");
if (use_message && !renew_authorship) { if (author_message) {
const char *a, *lb, *rb, *eol; const char *a, *lb, *rb, *eol;
a = strstr(use_message_buffer, "\nauthor "); a = strstr(author_message_buffer, "\nauthor ");
if (!a) if (!a)
die("invalid commit: %s", use_message); die("invalid commit: %s", author_message);
lb = strchrnul(a + strlen("\nauthor "), '<'); lb = strchrnul(a + strlen("\nauthor "), '<');
rb = strchrnul(lb, '>'); rb = strchrnul(lb, '>');
eol = strchrnul(rb, '\n'); eol = strchrnul(rb, '\n');
if (!*lb || !*rb || !*eol) if (!*lb || !*rb || !*eol)
die("invalid commit: %s", use_message); die("invalid commit: %s", author_message);
if (lb == a + strlen("\nauthor ")) if (lb == a + strlen("\nauthor "))
/* \nauthor <foo@example.com> */ /* \nauthor <foo@example.com> */
@ -641,11 +685,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} }
/* /*
* This final case does not modify the template message, * The remaining cases don't modify the template message, but
* it just sets the argument to the prepare-commit-msg hook. * just set the argument(s) to the prepare-commit-msg hook.
*/ */
else if (in_merge) else if (whence == FROM_MERGE)
hook_arg1 = "merge"; hook_arg1 = "merge";
else if (whence == FROM_CHERRY_PICK) {
hook_arg1 = "commit";
hook_arg2 = "CHERRY_PICK_HEAD";
}
if (squash_message) { if (squash_message) {
/* /*
@ -694,16 +742,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_addstr(&committer_ident, git_committer_info(0)); strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) { if (use_editor && include_status) {
char *ai_tmp, *ci_tmp; char *ai_tmp, *ci_tmp;
if (in_merge) if (whence != FROM_COMMIT)
fprintf(fp, fprintf(fp,
"#\n" "#\n"
"# It looks like you may be committing a MERGE.\n" "# It looks like you may be committing a %s.\n"
"# If this is not correct, please remove the file\n" "# If this is not correct, please remove the file\n"
"# %s\n" "# %s\n"
"# and try again.\n" "# and try again.\n"
"#\n", "#\n",
git_path("MERGE_HEAD")); whence_s(),
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
fprintf(fp, fprintf(fp,
"\n" "\n"
"# Please enter the commit message for your changes."); "# Please enter the commit message for your changes.");
@ -766,11 +816,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(fp); fclose(fp);
if (!commitable && !in_merge && !allow_empty && /*
* Reject an attempt to record a non-merge empty commit without
* explicit --allow-empty. In the cherry-pick case, it may be
* empty due to conflict resolution, which the user should okay.
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(head_sha1))) { !(amend && is_a_merge(head_sha1))) {
run_status(stdout, index_file, prefix, 0, s); run_status(stdout, index_file, prefix, 0, s);
if (amend) if (amend)
fputs(empty_amend_advice, stderr); fputs(empty_amend_advice, stderr);
else if (whence == FROM_CHERRY_PICK)
fputs(empty_cherry_pick_advice, stderr);
return 0; return 0;
} }
@ -898,6 +955,28 @@ static void handle_untracked_files_arg(struct wt_status *s)
die("Invalid untracked files mode '%s'", untracked_files_arg); die("Invalid untracked files mode '%s'", untracked_files_arg);
} }
static const char *read_commit_message(const char *name)
{
const char *out_enc, *out;
struct commit *commit;
commit = lookup_commit_reference_by_name(name);
if (!commit)
die("could not lookup commit %s", name);
out_enc = get_commit_output_encoding();
out = logmsg_reencode(commit, out_enc);
/*
* If we failed to reencode the buffer, just copy it
* byte for byte so the user can try to fix it up.
* This also handles the case where input and output
* encodings are identical.
*/
if (out == NULL)
out = xstrdup(commit->buffer);
return out;
}
static int parse_and_validate_options(int argc, const char *argv[], static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[], const char * const usage[],
const char *prefix, const char *prefix,
@ -927,8 +1006,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
/* Sanity check options */ /* Sanity check options */
if (amend && initial_commit) if (amend && initial_commit)
die("You have nothing to amend."); die("You have nothing to amend.");
if (amend && in_merge) if (amend && whence != FROM_COMMIT)
die("You are in the middle of a merge -- cannot amend."); die("You are in the middle of a %s -- cannot amend.", whence_s());
if (fixup_message && squash_message) if (fixup_message && squash_message)
die("Options --squash and --fixup cannot be used together"); die("Options --squash and --fixup cannot be used together");
if (use_message) if (use_message)
@ -947,26 +1026,18 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_message = edit_message; use_message = edit_message;
if (amend && !use_message && !fixup_message) if (amend && !use_message && !fixup_message)
use_message = "HEAD"; use_message = "HEAD";
if (!use_message && renew_authorship) if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
die("--reset-author can be used only with -C, -c or --amend."); die("--reset-author can be used only with -C, -c or --amend.");
if (use_message) { if (use_message) {
const char *out_enc; use_message_buffer = read_commit_message(use_message);
struct commit *commit; if (!renew_authorship) {
author_message = use_message;
commit = lookup_commit_reference_by_name(use_message); author_message_buffer = use_message_buffer;
if (!commit) }
die("could not lookup commit %s", use_message); }
out_enc = get_commit_output_encoding(); if (whence == FROM_CHERRY_PICK && !renew_authorship) {
use_message_buffer = logmsg_reencode(commit, out_enc); author_message = "CHERRY_PICK_HEAD";
author_message_buffer = read_commit_message(author_message);
/*
* If we failed to reencode the buffer, just copy it
* byte for byte so the user can try to fix it up.
* This also handles the case where input and output
* encodings are identical.
*/
if (use_message_buffer == NULL)
use_message_buffer = xstrdup(commit->buffer);
} }
if (!!also + !!only + !!all + !!interactive > 1) if (!!also + !!only + !!all + !!interactive > 1)
@ -1117,7 +1188,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s); wt_status_prepare(&s);
gitmodules_config(); gitmodules_config();
git_config(git_status_config, &s); git_config(git_status_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD")); determine_whence(&s);
argc = parse_options(argc, argv, prefix, argc = parse_options(argc, argv, prefix,
builtin_status_options, builtin_status_options,
builtin_status_usage, 0); builtin_status_usage, 0);
@ -1140,7 +1211,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
} }
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.in_merge = in_merge;
s.ignore_submodule_arg = ignore_submodule_arg; s.ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(&s); wt_status_collect(&s);
@ -1302,8 +1372,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s); wt_status_prepare(&s);
git_config(git_commit_config, &s); git_config(git_commit_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD")); determine_whence(&s);
s.in_merge = in_merge;
if (s.use_color == -1) if (s.use_color == -1)
s.use_color = git_use_color_default; s.use_color = git_use_color_default;
@ -1340,7 +1409,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
for (c = commit->parents; c; c = c->next) for (c = commit->parents; c; c = c->next)
pptr = &commit_list_insert(c->item, pptr)->next; pptr = &commit_list_insert(c->item, pptr)->next;
} else if (in_merge) { } else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT; struct strbuf m = STRBUF_INIT;
FILE *fp; FILE *fp;
@ -1369,7 +1438,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
parents = reduce_heads(parents); parents = reduce_heads(parents);
} else { } else {
if (!reflog_msg) if (!reflog_msg)
reflog_msg = "commit"; reflog_msg = (whence == FROM_CHERRY_PICK)
? "commit (cherry-pick)"
: "commit";
pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
} }
@ -1424,6 +1495,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"));

View file

@ -999,6 +999,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)

View file

@ -3,7 +3,6 @@
#include "object.h" #include "object.h"
#include "commit.h" #include "commit.h"
#include "tag.h" #include "tag.h"
#include "wt-status.h"
#include "run-command.h" #include "run-command.h"
#include "exec_cmd.h" #include "exec_cmd.h"
#include "utf8.h" #include "utf8.h"
@ -198,54 +197,20 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
strbuf_addstr(msgbuf, p); strbuf_addstr(msgbuf, p);
} }
static void set_author_ident_env(const char *message) static void write_cherry_pick_head(void)
{ {
const char *p = message; int fd;
if (!p) struct strbuf buf = STRBUF_INIT;
die ("Could not read commit message of %s",
sha1_to_hex(commit->object.sha1));
while (*p && *p != '\n') {
const char *eol;
for (eol = p; *eol && *eol != '\n'; eol++) strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
; /* do nothing */
if (!prefixcmp(p, "author ")) {
char *line, *pend, *email, *timestamp;
p += 7; fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
line = xmemdupz(p, eol - p); if (fd < 0)
email = strchr(line, '<'); die_errno("Could not open '%s' for writing",
if (!email) git_path("CHERRY_PICK_HEAD"));
die ("Could not extract author email from %s", if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
sha1_to_hex(commit->object.sha1)); die_errno("Could not write to '%s'", git_path("CHERRY_PICK_HEAD"));
if (email == line) strbuf_release(&buf);
pend = line;
else
for (pend = email; pend != line + 1 &&
isspace(pend[-1]); pend--);
; /* do nothing */
*pend = '\0';
email++;
timestamp = strchr(email, '>');
if (!timestamp)
die ("Could not extract author time from %s",
sha1_to_hex(commit->object.sha1));
*timestamp = '\0';
for (timestamp++; *timestamp && isspace(*timestamp);
timestamp++)
; /* do nothing */
setenv("GIT_AUTHOR_NAME", line, 1);
setenv("GIT_AUTHOR_EMAIL", email, 1);
setenv("GIT_AUTHOR_DATE", timestamp, 1);
free(line);
return;
}
p = eol;
if (*p == '\n')
p++;
}
die ("No author information found in %s",
sha1_to_hex(commit->object.sha1));
} }
static void advise(const char *advice, ...) static void advise(const char *advice, ...)
@ -263,15 +228,18 @@ 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;
} }
advise("after resolving the conflicts, mark the corrected paths"); advise("after resolving the conflicts, mark the corrected paths");
advise("with 'git add <paths>' or 'git rm <paths>'"); advise("with 'git add <paths>' or 'git rm <paths>'");
advise("and commit the result with 'git commit'");
if (action == CHERRY_PICK)
advise("and commit the result with 'git commit -c %s'",
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)
@ -497,13 +465,14 @@ static int do_pick_commit(void)
base_label = msg.parent_label; base_label = msg.parent_label;
next = commit; next = commit;
next_label = msg.label; next_label = msg.label;
set_author_ident_env(msg.message);
add_message_to_msg(&msgbuf, msg.message); add_message_to_msg(&msgbuf, msg.message);
if (no_replay) { if (no_replay) {
strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, "(cherry picked from commit ");
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) {

View file

@ -246,6 +246,8 @@ __git_ps1 ()
fi fi
elif [ -f "$g/MERGE_HEAD" ]; then elif [ -f "$g/MERGE_HEAD" ]; then
r="|MERGING" r="|MERGING"
elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
r="|CHERRY-PICKING"
elif [ -f "$g/BISECT_LOG" ]; then elif [ -f "$g/BISECT_LOG" ]; then
r="|BISECTING" r="|BISECTING"
fi fi

View file

@ -11,6 +11,18 @@ test_description='test cherry-pick and revert with conflicts
. ./test-lib.sh . ./test-lib.sh
test_cmp_rev () {
git rev-parse --verify "$1" >expect.rev &&
git rev-parse --verify "$2" >actual.rev &&
test_cmp expect.rev actual.rev
}
pristine_detach () {
git checkout -f "$1^0" &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x
}
test_expect_success setup ' test_expect_success setup '
echo unrelated >unrelated && echo unrelated >unrelated &&
@ -23,13 +35,7 @@ test_expect_success setup '
' '
test_expect_success 'failed cherry-pick does not advance HEAD' ' test_expect_success 'failed cherry-pick does not advance HEAD' '
pristine_detach initial &&
git checkout -f initial^0 &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
git update-index --refresh &&
git diff-index --exit-code HEAD &&
head=$(git rev-parse HEAD) && head=$(git rev-parse HEAD) &&
test_must_fail git cherry-pick picked && test_must_fail git cherry-pick picked &&
@ -39,33 +45,96 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
' '
test_expect_success 'advice from failed cherry-pick' " test_expect_success 'advice from failed cherry-pick' "
git checkout -f initial^0 && pristine_detach initial &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
git update-index --refresh &&
git diff-index --exit-code HEAD &&
picked=\$(git rev-parse --short picked) && picked=\$(git rev-parse --short picked) &&
cat <<-EOF >expected && cat <<-EOF >expected &&
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'
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 &&
git checkout -f initial^0 &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
git update-index --refresh &&
git diff-index --exit-code HEAD &&
test_must_fail git cherry-pick picked && test_must_fail git cherry-pick picked &&
@ -74,9 +143,7 @@ test_expect_success 'failed cherry-pick produces dirty index' '
' '
test_expect_success 'failed cherry-pick registers participants in index' ' test_expect_success 'failed cherry-pick registers participants in index' '
pristine_detach initial &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
{ {
git checkout base -- foo && git checkout base -- foo &&
git ls-files --stage foo && git ls-files --stage foo &&
@ -90,10 +157,7 @@ test_expect_success 'failed cherry-pick registers participants in index' '
2 s/ 0 / 2 / 2 s/ 0 / 2 /
3 s/ 0 / 3 / 3 s/ 0 / 3 /
" < stages > expected && " < stages > expected &&
git checkout -f initial^0 && git read-tree -u --reset HEAD &&
git update-index --refresh &&
git diff-index --exit-code HEAD &&
test_must_fail git cherry-pick picked && test_must_fail git cherry-pick picked &&
git ls-files --stage --unmerged > actual && git ls-files --stage --unmerged > actual &&
@ -102,10 +166,7 @@ test_expect_success 'failed cherry-pick registers participants in index' '
' '
test_expect_success 'failed cherry-pick describes conflict in work tree' ' test_expect_success 'failed cherry-pick describes conflict in work tree' '
pristine_detach initial &&
git checkout -f initial^0 &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
cat <<-EOF > expected && cat <<-EOF > expected &&
<<<<<<< HEAD <<<<<<< HEAD
a a
@ -114,9 +175,6 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' '
>>>>>>> objid picked >>>>>>> objid picked
EOF EOF
git update-index --refresh &&
git diff-index --exit-code HEAD &&
test_must_fail git cherry-pick picked && test_must_fail git cherry-pick picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
@ -124,11 +182,8 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' '
' '
test_expect_success 'diff3 -m style' ' test_expect_success 'diff3 -m style' '
pristine_detach initial &&
git config merge.conflictstyle diff3 && git config merge.conflictstyle diff3 &&
git checkout -f initial^0 &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
cat <<-EOF > expected && cat <<-EOF > expected &&
<<<<<<< HEAD <<<<<<< HEAD
a a
@ -139,9 +194,6 @@ test_expect_success 'diff3 -m style' '
>>>>>>> objid picked >>>>>>> objid picked
EOF EOF
git update-index --refresh &&
git diff-index --exit-code HEAD &&
test_must_fail git cherry-pick picked && test_must_fail git cherry-pick picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
@ -149,10 +201,8 @@ test_expect_success 'diff3 -m style' '
' '
test_expect_success 'revert also handles conflicts sanely' ' test_expect_success 'revert also handles conflicts sanely' '
git config --unset merge.conflictstyle && git config --unset merge.conflictstyle &&
git read-tree -u --reset HEAD && pristine_detach initial &&
git clean -d -f -f -q -x &&
cat <<-EOF > expected && cat <<-EOF > expected &&
<<<<<<< HEAD <<<<<<< HEAD
a a
@ -173,10 +223,7 @@ test_expect_success 'revert also handles conflicts sanely' '
2 s/ 0 / 2 / 2 s/ 0 / 2 /
3 s/ 0 / 3 / 3 s/ 0 / 3 /
" < stages > expected-stages && " < stages > expected-stages &&
git checkout -f initial^0 && git read-tree -u --reset HEAD &&
git update-index --refresh &&
git diff-index --exit-code HEAD &&
head=$(git rev-parse HEAD) && head=$(git rev-parse HEAD) &&
test_must_fail git revert picked && test_must_fail git revert picked &&
@ -192,10 +239,8 @@ test_expect_success 'revert also handles conflicts sanely' '
' '
test_expect_success 'revert conflict, diff3 -m style' ' test_expect_success 'revert conflict, diff3 -m style' '
pristine_detach initial &&
git config merge.conflictstyle diff3 && git config merge.conflictstyle diff3 &&
git checkout -f initial^0 &&
git read-tree -u --reset HEAD &&
git clean -d -f -f -q -x &&
cat <<-EOF > expected && cat <<-EOF > expected &&
<<<<<<< HEAD <<<<<<< HEAD
a a
@ -206,9 +251,6 @@ test_expect_success 'revert conflict, diff3 -m style' '
>>>>>>> parent of objid picked >>>>>>> parent of objid picked
EOF EOF
git update-index --refresh &&
git diff-index --exit-code HEAD &&
test_must_fail git revert picked && test_must_fail git revert picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&

View file

@ -157,4 +157,33 @@ test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
test_must_fail git commit -a --reset-author -m done test_must_fail git commit -a --reset-author -m done
' '
test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' '
echo "cherry-pick 1a" >>foo &&
test_tick &&
git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" &&
git tag cherry-pick-head &&
git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
echo "This is a MERGE_MSG" >.git/MERGE_MSG &&
echo "cherry-pick 1b" >>foo &&
test_tick &&
git commit -a &&
author_header cherry-pick-head >expect &&
author_header HEAD >actual &&
test_cmp expect actual &&
echo "This is a MERGE_MSG" >expect &&
message_body HEAD >actual &&
test_cmp expect actual
'
test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
echo "cherry-pick 2" >>foo &&
test_tick &&
git commit -am "cherry-pick 2" --reset-author &&
echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
author_header HEAD >actual &&
test_cmp expect actual
'
test_done test_done

View file

@ -60,7 +60,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
color_fprintf_ln(s->fp, c, "# Unmerged paths:"); color_fprintf_ln(s->fp, c, "# Unmerged paths:");
if (!advice_status_hints) if (!advice_status_hints)
return; return;
if (s->in_merge) if (s->whence != FROM_COMMIT)
; ;
else if (!s->is_initial) else if (!s->is_initial)
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
@ -77,7 +77,7 @@ static void wt_status_print_cached_header(struct wt_status *s)
color_fprintf_ln(s->fp, c, "# Changes to be committed:"); color_fprintf_ln(s->fp, c, "# Changes to be committed:");
if (!advice_status_hints) if (!advice_status_hints)
return; return;
if (s->in_merge) if (s->whence != FROM_COMMIT)
; /* NEEDSWORK: use "git reset --unresolve"??? */ ; /* NEEDSWORK: use "git reset --unresolve"??? */
else if (!s->is_initial) else if (!s->is_initial)
color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);

View file

@ -24,6 +24,13 @@ enum untracked_status_type {
SHOW_ALL_UNTRACKED_FILES SHOW_ALL_UNTRACKED_FILES
}; };
/* from where does this commit originate */
enum commit_whence {
FROM_COMMIT, /* normal */
FROM_MERGE, /* commit came from merge */
FROM_CHERRY_PICK /* commit came from cherry-pick */
};
struct wt_status_change_data { struct wt_status_change_data {
int worktree_status; int worktree_status;
int index_status; int index_status;
@ -40,7 +47,7 @@ struct wt_status {
const char **pathspec; const char **pathspec;
int verbose; int verbose;
int amend; int amend;
int in_merge; enum commit_whence whence;
int nowarn; int nowarn;
int use_color; int use_color;
int relative_paths; int relative_paths;