Merge branch 'cc/cherry-pick-series'
* cc/cherry-pick-series: Documentation/revert: describe passing more than one commit Documentation/cherry-pick: describe passing more than one commit revert: add tests to check cherry-picking many commits revert: allow cherry-picking more than one commit revert: change help_msg() to take no argument revert: refactor code into a do_pick_commit() function revert: use run_command_v_opt() instead of execv_git_cmd() revert: cleanup code for -x option
This commit is contained in:
commit
8c7da8690d
@ -3,24 +3,28 @@ git-cherry-pick(1)
|
||||
|
||||
NAME
|
||||
----
|
||||
git-cherry-pick - Apply the change introduced by an existing commit
|
||||
git-cherry-pick - Apply the changes introduced by some existing commits
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>
|
||||
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Given one existing commit, apply the change the patch introduces, and record a
|
||||
new commit that records it. This requires your working tree to be clean (no
|
||||
modifications from the HEAD commit).
|
||||
|
||||
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).
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
<commit>::
|
||||
Commit to cherry-pick.
|
||||
<commit>...::
|
||||
Commits to cherry-pick.
|
||||
For a more complete list of ways to spell commits, see the
|
||||
"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
|
||||
Sets of commits can be passed but no traversal is done by
|
||||
default, as if the '--no-walk' option was specified, see
|
||||
linkgit:git-rev-list[1].
|
||||
|
||||
-e::
|
||||
--edit::
|
||||
@ -55,10 +59,10 @@ OPTIONS
|
||||
|
||||
-n::
|
||||
--no-commit::
|
||||
Usually the command automatically creates a commit.
|
||||
This flag applies the change necessary to cherry-pick
|
||||
the named commit to your working tree and the index,
|
||||
but does not make the commit. In addition, when this
|
||||
Usually the command automatically creates a sequence of commits.
|
||||
This flag applies the changes necessary to cherry-pick
|
||||
each named commit to your working tree and the index,
|
||||
without making any commit. In addition, when this
|
||||
option is used, your index does not have to match the
|
||||
HEAD commit. The cherry-pick is done against the
|
||||
beginning state of your index.
|
||||
@ -75,6 +79,40 @@ effect to your index in a row.
|
||||
cherry-pick'ed commit, then a fast forward to this commit will
|
||||
be performed.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
git cherry-pick master::
|
||||
|
||||
Apply the change introduced by the commit at the tip of the
|
||||
master branch and create a new commit with this change.
|
||||
|
||||
git cherry-pick ..master::
|
||||
git cherry-pick ^HEAD master::
|
||||
|
||||
Apply the changes introduced by all commits that are ancestors
|
||||
of master but not of HEAD to produce new commits.
|
||||
|
||||
git cherry-pick master\~4 master~2::
|
||||
|
||||
Apply the changes introduced by the fifth and third last
|
||||
commits pointed to by master and create 2 new commits with
|
||||
these changes.
|
||||
|
||||
git cherry-pick -n master~1 next::
|
||||
|
||||
Apply to the working tree and the index the changes introduced
|
||||
by the second last commit pointed to by master and by the last
|
||||
commit pointed to by next, but do not create any commit with
|
||||
these changes.
|
||||
|
||||
git cherry-pick --ff ..next::
|
||||
|
||||
If history is linear and HEAD is an ancestor of next, update
|
||||
the working tree and advance the HEAD pointer to match next.
|
||||
Otherwise, apply the changes introduced by those commits that
|
||||
are in next but not HEAD to the current branch, creating a new
|
||||
commit for each new change.
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <gitster@pobox.com>
|
||||
@ -83,6 +121,10 @@ Documentation
|
||||
--------------
|
||||
Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-revert[1]
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
||||
|
@ -3,20 +3,22 @@ git-revert(1)
|
||||
|
||||
NAME
|
||||
----
|
||||
git-revert - Revert an existing commit
|
||||
git-revert - Revert some existing commits
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>
|
||||
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Given one existing commit, revert the change the patch introduces, and record a
|
||||
new commit that records it. This requires your working tree to be clean (no
|
||||
modifications from the HEAD commit).
|
||||
|
||||
Note: 'git revert' is used to record a new commit to reverse the
|
||||
effect of an earlier commit (often a faulty one). If you want to
|
||||
Given one or more existing commits, revert the changes that the
|
||||
related patches introduce, and record some new commits that record
|
||||
them. This requires your working tree to be clean (no modifications
|
||||
from the HEAD commit).
|
||||
|
||||
Note: 'git revert' is used to record some new commits to reverse the
|
||||
effect of some earlier commits (often only a faulty one). If you want to
|
||||
throw away all uncommitted changes in your working directory, you
|
||||
should see linkgit:git-reset[1], particularly the '--hard' option. If
|
||||
you want to extract specific files as they were in another commit, you
|
||||
@ -26,10 +28,13 @@ both will discard uncommitted changes in your working directory.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
<commit>::
|
||||
Commit to revert.
|
||||
<commit>...::
|
||||
Commits to revert.
|
||||
For a more complete list of ways to spell commit names, see
|
||||
"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
|
||||
Sets of commits can also be given but no traversal is done by
|
||||
default, see linkgit:git-rev-list[1] and its '--no-walk'
|
||||
option.
|
||||
|
||||
-e::
|
||||
--edit::
|
||||
@ -59,11 +64,11 @@ more details.
|
||||
|
||||
-n::
|
||||
--no-commit::
|
||||
Usually the command automatically creates a commit with
|
||||
a commit log message stating which commit was
|
||||
reverted. This flag applies the change necessary
|
||||
to revert the named commit to your working tree
|
||||
and the index, but does not make the commit. In addition,
|
||||
Usually the command automatically creates some commits with
|
||||
commit log messages stating which commits were
|
||||
reverted. This flag applies the changes necessary
|
||||
to revert the named commits to your working tree
|
||||
and the index, but does not make the commits. In addition,
|
||||
when this option is used, your index does not have to match
|
||||
the HEAD commit. The revert is done against the
|
||||
beginning state of your index.
|
||||
@ -75,6 +80,20 @@ effect to your index in a row.
|
||||
--signoff::
|
||||
Add Signed-off-by line at the end of the commit message.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
git revert HEAD~3::
|
||||
|
||||
Revert the changes specified by the fourth last commit in HEAD
|
||||
and create a new commit with the reverted changes.
|
||||
|
||||
git revert -n master\~5..master~2::
|
||||
|
||||
Revert the changes done by commits from the fifth last commit
|
||||
in master (included) to the third last commit in master
|
||||
(included), but do not create any commit with the reverted
|
||||
changes. The revert only modifies the working tree and the
|
||||
index.
|
||||
|
||||
Author
|
||||
------
|
||||
@ -84,6 +103,10 @@ Documentation
|
||||
--------------
|
||||
Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-cherry-pick[1]
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
||||
|
120
builtin/revert.c
120
builtin/revert.c
@ -39,7 +39,8 @@ static const char * const cherry_pick_usage[] = {
|
||||
static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
|
||||
static enum { REVERT, CHERRY_PICK } action;
|
||||
static struct commit *commit;
|
||||
static const char *commit_name;
|
||||
static int commit_argc;
|
||||
static const char **commit_argv;
|
||||
static int allow_rerere_auto;
|
||||
|
||||
static const char *me;
|
||||
@ -53,12 +54,10 @@ static void parse_args(int argc, const char **argv)
|
||||
{
|
||||
const char * const * usage_str =
|
||||
action == REVERT ? revert_usage : cherry_pick_usage;
|
||||
unsigned char sha1[20];
|
||||
int noop;
|
||||
struct option options[] = {
|
||||
OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
|
||||
OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
|
||||
OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
|
||||
OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
|
||||
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
|
||||
OPT_INTEGER('m', "mainline", &mainline, "parent number"),
|
||||
@ -71,6 +70,7 @@ static void parse_args(int argc, const char **argv)
|
||||
|
||||
if (action == CHERRY_PICK) {
|
||||
struct option cp_extra[] = {
|
||||
OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
|
||||
OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
|
||||
OPT_END(),
|
||||
};
|
||||
@ -78,15 +78,11 @@ static void parse_args(int argc, const char **argv)
|
||||
die("program error");
|
||||
}
|
||||
|
||||
if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
|
||||
commit_argc = parse_options(argc, argv, NULL, options, usage_str, 0);
|
||||
if (commit_argc < 1)
|
||||
usage_with_options(usage_str, options);
|
||||
|
||||
commit_name = argv[0];
|
||||
if (get_sha1(commit_name, sha1))
|
||||
die ("Cannot find '%s'", commit_name);
|
||||
commit = lookup_commit_reference(sha1);
|
||||
if (!commit)
|
||||
exit(1);
|
||||
commit_argv = argv;
|
||||
}
|
||||
|
||||
struct commit_message {
|
||||
@ -239,7 +235,7 @@ static void set_author_ident_env(const char *message)
|
||||
sha1_to_hex(commit->object.sha1));
|
||||
}
|
||||
|
||||
static char *help_msg(const char *name)
|
||||
static char *help_msg(void)
|
||||
{
|
||||
struct strbuf helpbuf = STRBUF_INIT;
|
||||
char *msg = getenv("GIT_CHERRY_PICK_HELP");
|
||||
@ -255,7 +251,7 @@ static char *help_msg(const char *name)
|
||||
strbuf_addf(&helpbuf, " with: \n"
|
||||
"\n"
|
||||
" git commit -c %s\n",
|
||||
name);
|
||||
sha1_to_hex(commit->object.sha1));
|
||||
}
|
||||
else
|
||||
strbuf_addch(&helpbuf, '.');
|
||||
@ -357,7 +353,7 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
|
||||
}
|
||||
write_message(msgbuf, defmsg);
|
||||
fprintf(stderr, "Automatic %s failed.%s\n",
|
||||
me, help_msg(commit_name));
|
||||
me, help_msg());
|
||||
rerere(allow_rerere_auto);
|
||||
exit(1);
|
||||
}
|
||||
@ -365,7 +361,7 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
|
||||
fprintf(stderr, "Finished one %s.\n", me);
|
||||
}
|
||||
|
||||
static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
static int do_pick_commit(void)
|
||||
{
|
||||
unsigned char head[20];
|
||||
struct commit *base, *next, *parent;
|
||||
@ -374,28 +370,6 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
char *defmsg = NULL;
|
||||
struct strbuf msgbuf = STRBUF_INIT;
|
||||
|
||||
git_config(git_default_config, NULL);
|
||||
me = action == REVERT ? "revert" : "cherry-pick";
|
||||
setenv(GIT_REFLOG_ACTION, me, 0);
|
||||
parse_args(argc, argv);
|
||||
|
||||
/* this is copied from the shell script, but it's never triggered... */
|
||||
if (action == REVERT && !no_replay)
|
||||
die("revert is incompatible with replay");
|
||||
|
||||
if (allow_ff) {
|
||||
if (signoff)
|
||||
die("cherry-pick --ff cannot be used with --signoff");
|
||||
if (no_commit)
|
||||
die("cherry-pick --ff cannot be used with --no-commit");
|
||||
if (no_replay)
|
||||
die("cherry-pick --ff cannot be used with -x");
|
||||
if (edit)
|
||||
die("cherry-pick --ff cannot be used with --edit");
|
||||
}
|
||||
|
||||
if (read_cache() < 0)
|
||||
die("git %s: failed to read the index", me);
|
||||
if (no_commit) {
|
||||
/*
|
||||
* We do not intend to commit immediately. We just want to
|
||||
@ -506,12 +480,14 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
free_commit_list(remotes);
|
||||
if (res) {
|
||||
fprintf(stderr, "Automatic %s with strategy %s failed.%s\n",
|
||||
me, strategy, help_msg(commit_name));
|
||||
me, strategy, help_msg());
|
||||
rerere(allow_rerere_auto);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
free_message(&msg);
|
||||
|
||||
/*
|
||||
*
|
||||
* If we are cherry-pick, and if the merge did not result in
|
||||
@ -524,7 +500,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
if (!no_commit) {
|
||||
/* 6 is max possible length of our args array including NULL */
|
||||
const char *args[6];
|
||||
int res;
|
||||
int i = 0;
|
||||
|
||||
args[i++] = "commit";
|
||||
args[i++] = "-n";
|
||||
if (signoff)
|
||||
@ -534,26 +512,86 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
args[i++] = defmsg;
|
||||
}
|
||||
args[i] = NULL;
|
||||
return execv_git_cmd(args);
|
||||
res = run_command_v_opt(args, RUN_GIT_CMD);
|
||||
free(defmsg);
|
||||
|
||||
return res;
|
||||
}
|
||||
free_message(&msg);
|
||||
|
||||
free(defmsg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void prepare_revs(struct rev_info *revs)
|
||||
{
|
||||
int argc = 0;
|
||||
int i;
|
||||
const char **argv = xmalloc((commit_argc + 4) * sizeof(*argv));
|
||||
|
||||
argv[argc++] = NULL;
|
||||
argv[argc++] = "--no-walk";
|
||||
if (action != REVERT)
|
||||
argv[argc++] = "--reverse";
|
||||
for (i = 0; i < commit_argc; i++)
|
||||
argv[argc++] = commit_argv[i];
|
||||
argv[argc++] = NULL;
|
||||
|
||||
init_revisions(revs, NULL);
|
||||
setup_revisions(argc - 1, argv, revs, NULL);
|
||||
if (prepare_revision_walk(revs))
|
||||
die("revision walk setup failed");
|
||||
|
||||
if (!revs->commits)
|
||||
die("empty commit set passed");
|
||||
|
||||
free(argv);
|
||||
}
|
||||
|
||||
static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
{
|
||||
struct rev_info revs;
|
||||
|
||||
git_config(git_default_config, NULL);
|
||||
me = action == REVERT ? "revert" : "cherry-pick";
|
||||
setenv(GIT_REFLOG_ACTION, me, 0);
|
||||
parse_args(argc, argv);
|
||||
|
||||
if (allow_ff) {
|
||||
if (signoff)
|
||||
die("cherry-pick --ff cannot be used with --signoff");
|
||||
if (no_commit)
|
||||
die("cherry-pick --ff cannot be used with --no-commit");
|
||||
if (no_replay)
|
||||
die("cherry-pick --ff cannot be used with -x");
|
||||
if (edit)
|
||||
die("cherry-pick --ff cannot be used with --edit");
|
||||
}
|
||||
|
||||
if (read_cache() < 0)
|
||||
die("git %s: failed to read the index", me);
|
||||
|
||||
prepare_revs(&revs);
|
||||
|
||||
while ((commit = get_revision(&revs))) {
|
||||
int res = do_pick_commit();
|
||||
if (res)
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_revert(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
if (isatty(0))
|
||||
edit = 1;
|
||||
no_replay = 1;
|
||||
action = REVERT;
|
||||
return revert_or_cherry_pick(argc, argv);
|
||||
}
|
||||
|
||||
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
no_replay = 0;
|
||||
action = CHERRY_PICK;
|
||||
return revert_or_cherry_pick(argc, argv);
|
||||
}
|
||||
|
95
t/t3508-cherry-pick-many-commits.sh
Executable file
95
t/t3508-cherry-pick-many-commits.sh
Executable file
@ -0,0 +1,95 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='test cherry-picking many commits'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success setup '
|
||||
echo first > file1 &&
|
||||
git add file1 &&
|
||||
test_tick &&
|
||||
git commit -m "first" &&
|
||||
git tag first &&
|
||||
|
||||
git checkout -b other &&
|
||||
for val in second third fourth
|
||||
do
|
||||
echo $val >> file1 &&
|
||||
git add file1 &&
|
||||
test_tick &&
|
||||
git commit -m "$val" &&
|
||||
git tag $val
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick first..fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard first &&
|
||||
test_tick &&
|
||||
git cherry-pick first..fourth &&
|
||||
git diff --quiet other &&
|
||||
git diff --quiet HEAD other &&
|
||||
test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)"
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick --ff first..fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard first &&
|
||||
test_tick &&
|
||||
git cherry-pick --ff first..fourth &&
|
||||
git diff --quiet other &&
|
||||
git diff --quiet HEAD other &&
|
||||
test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify fourth)"
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick -n first..fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard first &&
|
||||
test_tick &&
|
||||
git cherry-pick -n first..fourth &&
|
||||
git diff --quiet other &&
|
||||
git diff --cached --quiet other &&
|
||||
git diff --quiet HEAD first
|
||||
'
|
||||
|
||||
test_expect_success 'revert first..fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard fourth &&
|
||||
test_tick &&
|
||||
git revert first..fourth &&
|
||||
git diff --quiet first &&
|
||||
git diff --cached --quiet first &&
|
||||
git diff --quiet HEAD first
|
||||
'
|
||||
|
||||
test_expect_success 'revert ^first fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard fourth &&
|
||||
test_tick &&
|
||||
git revert ^first fourth &&
|
||||
git diff --quiet first &&
|
||||
git diff --cached --quiet first &&
|
||||
git diff --quiet HEAD first
|
||||
'
|
||||
|
||||
test_expect_success 'revert fourth fourth~1 fourth~2 works' '
|
||||
git checkout master &&
|
||||
git reset --hard fourth &&
|
||||
test_tick &&
|
||||
git revert fourth fourth~1 fourth~2 &&
|
||||
git diff --quiet first &&
|
||||
git diff --cached --quiet first &&
|
||||
git diff --quiet HEAD first
|
||||
'
|
||||
|
||||
test_expect_failure 'cherry-pick -3 fourth works' '
|
||||
git checkout master &&
|
||||
git reset --hard first &&
|
||||
test_tick &&
|
||||
git cherry-pick -3 fourth &&
|
||||
git diff --quiet other &&
|
||||
git diff --quiet HEAD other &&
|
||||
test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)"
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user