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:
commit
99f5b0845a
@ -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
|
||||
------
|
||||
|
@ -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;
|
||||
|
@ -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, ' ');
|
||||
|
||||
|
3
cache.h
3
cache.h
@ -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 */
|
||||
|
@ -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 () {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
98
t/t3506-cherry-pick-ff.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user