Merge branch 'cc/cherry-pick-ff'

* cc/cherry-pick-ff:
  revert: fix tiny memory leak in cherry-pick --ff
  rebase -i: use new --ff cherry-pick option
  Documentation: describe new cherry-pick --ff option
  cherry-pick: add tests for new --ff option
  revert: add --ff option to allow fast forward when cherry-picking
  builtin/merge: make checkout_fast_forward() non static
  parse-options: add parse_options_concat() to concat options
This commit is contained in:
Junio C Hamano 2010-03-28 21:52:28 -07:00
commit 99f5b0845a
8 changed files with 168 additions and 18 deletions

View File

@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit
SYNOPSIS
--------
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] <commit>
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>
DESCRIPTION
-----------
@ -70,6 +70,10 @@ effect to your index in a row.
--signoff::
Add Signed-off-by line at the end of the commit message.
--ff::
If the current HEAD is the same as the parent of the
cherry-pick'ed commit, then a fast forward to this commit will
be performed.
Author
------

View File

@ -667,7 +667,7 @@ static int count_unmerged_entries(void)
return ret;
}
static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
int checkout_fast_forward(const unsigned char *head, const unsigned char *remote)
{
struct tree *trees[MAX_UNPACK_TREES];
struct unpack_trees_options opts;

View File

@ -13,6 +13,7 @@
#include "revision.h"
#include "rerere.h"
#include "merge-recursive.h"
#include "refs.h"
/*
* This implements the builtins revert and cherry-pick.
@ -35,7 +36,7 @@ static const char * const cherry_pick_usage[] = {
NULL
};
static int edit, no_replay, no_commit, mainline, signoff;
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;
@ -60,8 +61,19 @@ static void parse_args(int argc, const char **argv)
OPT_INTEGER('m', "mainline", &mainline, "parent number"),
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_END(),
OPT_END(),
OPT_END(),
};
if (action == CHERRY_PICK) {
struct option cp_extra[] = {
OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
OPT_END(),
};
if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
die("program error");
}
if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
usage_with_options(usage_str, options);
@ -244,6 +256,17 @@ static NORETURN void die_dirty_index(const char *me)
}
}
static int fast_forward_to(const unsigned char *to, const unsigned char *from)
{
struct ref_lock *ref_lock;
read_cache();
if (checkout_fast_forward(from, to))
exit(1); /* the callee should have complained already */
ref_lock = lock_any_ref_for_update("HEAD", from, 0);
return write_ref_sha1(ref_lock, to, "cherry-pick");
}
static int revert_or_cherry_pick(int argc, const char **argv)
{
unsigned char head[20];
@ -251,7 +274,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
int i, index_fd, clean;
char *oneline, *reencoded_message = NULL;
const char *message, *encoding;
char *defmsg = git_pathdup("MERGE_MSG");
char *defmsg = NULL;
struct merge_options o;
struct tree *result, *next_tree, *base_tree, *head_tree;
static struct lock_file index_lock;
@ -265,6 +288,17 @@ static int revert_or_cherry_pick(int argc, const char **argv)
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) {
@ -284,8 +318,6 @@ static int revert_or_cherry_pick(int argc, const char **argv)
}
discard_cache();
index_fd = hold_locked_index(&index_lock, 1);
if (!commit->parents) {
if (action == REVERT)
die ("Cannot revert a root commit");
@ -314,6 +346,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
else
parent = commit->parents->item;
if (allow_ff && !hashcmp(parent->object.sha1, head))
return fast_forward_to(commit->object.sha1, head);
if (!(message = commit->buffer))
die ("Cannot get commit message for %s",
sha1_to_hex(commit->object.sha1));
@ -329,6 +364,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
* reverse of it if we are revert.
*/
defmsg = git_pathdup("MERGE_MSG");
msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
LOCK_DIE_ON_ERROR);
@ -343,6 +379,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
oneline = get_oneline(message);
index_fd = hold_locked_index(&index_lock, 1);
if (action == REVERT) {
char *oneline_body = strchr(oneline, ' ');

View File

@ -1058,4 +1058,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias);
int split_cmdline(char *cmdline, const char ***argv);
/* builtin/merge.c */
int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
#endif /* CACHE_H */

View File

@ -230,8 +230,8 @@ do_with_author () {
}
pick_one () {
no_ff=
case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
ff=--ff
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
test -d "$REWRITTEN" &&
pick_one_preserving_merges "$@" && return
@ -240,16 +240,7 @@ pick_one () {
output git cherry-pick "$@"
return
fi
parent_sha1=$(git rev-parse --verify $sha1^) ||
die "Could not get the parent of $sha1"
current_sha1=$(git rev-parse --verify HEAD)
if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1"
then
output git reset --hard $sha1
output warn Fast-forward to $(git rev-parse --short $sha1)
else
output git cherry-pick "$@"
fi
output git cherry-pick $ff "$@"
}
pick_one_preserving_merges () {

View File

@ -659,3 +659,18 @@ int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
*target = unset ? 2 : 1;
return 0;
}
int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
{
int i, j;
for (i = 0; i < dst_size; i++)
if (dst[i].type == OPTION_END)
break;
for (j = 0; i < dst_size; i++, j++) {
dst[i] = src[j];
if (src[j].type == OPTION_END)
return 0;
}
return -1;
}

View File

@ -187,6 +187,7 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,
extern int parse_options_end(struct parse_opt_ctx_t *ctx);
extern int parse_options_concat(struct option *dst, size_t, struct option *src);
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);

98
t/t3506-cherry-pick-ff.sh Executable file
View File

@ -0,0 +1,98 @@
#!/bin/sh
test_description='test cherry-picking with --ff option'
. ./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 &&
echo second >> file1 &&
git add file1 &&
test_tick &&
git commit -m "second" &&
git tag second
'
test_expect_success 'cherry-pick using --ff fast forwards' '
git checkout master &&
git reset --hard first &&
test_tick &&
git cherry-pick --ff second &&
test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify second)"
'
test_expect_success 'cherry-pick not using --ff does not fast forwards' '
git checkout master &&
git reset --hard first &&
test_tick &&
git cherry-pick second &&
test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify second)"
'
#
# We setup the following graph:
#
# B---C
# / /
# first---A
#
# (This has been taken from t3502-cherry-pick-merge.sh)
#
test_expect_success 'merge setup' '
git checkout master &&
git reset --hard first &&
echo new line >A &&
git add A &&
test_tick &&
git commit -m "add line to A" A &&
git tag A &&
git checkout -b side first &&
echo new line >B &&
git add B &&
test_tick &&
git commit -m "add line to B" B &&
git tag B &&
git checkout master &&
git merge side &&
git tag C &&
git checkout -b new A
'
test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' '
git reset --hard A -- &&
test_must_fail git cherry-pick --ff -m 1 B &&
git diff --exit-code A --
'
test_expect_success 'cherry pick a merge with --ff but without -m should fail' '
git reset --hard A -- &&
test_must_fail git cherry-pick --ff C &&
git diff --exit-code A --
'
test_expect_success 'cherry pick with --ff a merge (1)' '
git reset --hard A -- &&
git cherry-pick --ff -m 1 C &&
git diff --exit-code C &&
test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
'
test_expect_success 'cherry pick with --ff a merge (2)' '
git reset --hard B -- &&
git cherry-pick --ff -m 2 C &&
git diff --exit-code C &&
test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
'
test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' '
git reset --hard B -- &&
test_must_fail git cherry-pick --ff -m 3 C
'
test_done