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
|
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
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -70,6 +70,10 @@ effect to your index in a row.
|
|||||||
--signoff::
|
--signoff::
|
||||||
Add Signed-off-by line at the end of the commit message.
|
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
|
Author
|
||||||
------
|
------
|
||||||
|
@ -667,7 +667,7 @@ static int count_unmerged_entries(void)
|
|||||||
return ret;
|
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 tree *trees[MAX_UNPACK_TREES];
|
||||||
struct unpack_trees_options opts;
|
struct unpack_trees_options opts;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "rerere.h"
|
#include "rerere.h"
|
||||||
#include "merge-recursive.h"
|
#include "merge-recursive.h"
|
||||||
|
#include "refs.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This implements the builtins revert and cherry-pick.
|
* This implements the builtins revert and cherry-pick.
|
||||||
@ -35,7 +36,7 @@ static const char * const cherry_pick_usage[] = {
|
|||||||
NULL
|
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 enum { REVERT, CHERRY_PICK } action;
|
||||||
static struct commit *commit;
|
static struct commit *commit;
|
||||||
static const char *commit_name;
|
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_INTEGER('m', "mainline", &mainline, "parent number"),
|
||||||
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
|
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
|
||||||
OPT_END(),
|
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)
|
if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
|
||||||
usage_with_options(usage_str, options);
|
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)
|
static int revert_or_cherry_pick(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
unsigned char head[20];
|
unsigned char head[20];
|
||||||
@ -251,7 +274,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
|||||||
int i, index_fd, clean;
|
int i, index_fd, clean;
|
||||||
char *oneline, *reencoded_message = NULL;
|
char *oneline, *reencoded_message = NULL;
|
||||||
const char *message, *encoding;
|
const char *message, *encoding;
|
||||||
char *defmsg = git_pathdup("MERGE_MSG");
|
char *defmsg = NULL;
|
||||||
struct merge_options o;
|
struct merge_options o;
|
||||||
struct tree *result, *next_tree, *base_tree, *head_tree;
|
struct tree *result, *next_tree, *base_tree, *head_tree;
|
||||||
static struct lock_file index_lock;
|
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)
|
if (action == REVERT && !no_replay)
|
||||||
die("revert is incompatible with 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)
|
if (read_cache() < 0)
|
||||||
die("git %s: failed to read the index", me);
|
die("git %s: failed to read the index", me);
|
||||||
if (no_commit) {
|
if (no_commit) {
|
||||||
@ -284,8 +318,6 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
|||||||
}
|
}
|
||||||
discard_cache();
|
discard_cache();
|
||||||
|
|
||||||
index_fd = hold_locked_index(&index_lock, 1);
|
|
||||||
|
|
||||||
if (!commit->parents) {
|
if (!commit->parents) {
|
||||||
if (action == REVERT)
|
if (action == REVERT)
|
||||||
die ("Cannot revert a root commit");
|
die ("Cannot revert a root commit");
|
||||||
@ -314,6 +346,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
|||||||
else
|
else
|
||||||
parent = commit->parents->item;
|
parent = commit->parents->item;
|
||||||
|
|
||||||
|
if (allow_ff && !hashcmp(parent->object.sha1, head))
|
||||||
|
return fast_forward_to(commit->object.sha1, head);
|
||||||
|
|
||||||
if (!(message = commit->buffer))
|
if (!(message = commit->buffer))
|
||||||
die ("Cannot get commit message for %s",
|
die ("Cannot get commit message for %s",
|
||||||
sha1_to_hex(commit->object.sha1));
|
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.
|
* reverse of it if we are revert.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
defmsg = git_pathdup("MERGE_MSG");
|
||||||
msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
|
msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
|
||||||
LOCK_DIE_ON_ERROR);
|
LOCK_DIE_ON_ERROR);
|
||||||
|
|
||||||
@ -343,6 +379,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
|||||||
|
|
||||||
oneline = get_oneline(message);
|
oneline = get_oneline(message);
|
||||||
|
|
||||||
|
index_fd = hold_locked_index(&index_lock, 1);
|
||||||
|
|
||||||
if (action == REVERT) {
|
if (action == REVERT) {
|
||||||
char *oneline_body = strchr(oneline, ' ');
|
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);
|
char *alias_lookup(const char *alias);
|
||||||
int split_cmdline(char *cmdline, const char ***argv);
|
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 */
|
#endif /* CACHE_H */
|
||||||
|
@ -230,8 +230,8 @@ do_with_author () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pick_one () {
|
pick_one () {
|
||||||
no_ff=
|
ff=--ff
|
||||||
case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
|
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
|
||||||
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
|
||||||
test -d "$REWRITTEN" &&
|
test -d "$REWRITTEN" &&
|
||||||
pick_one_preserving_merges "$@" && return
|
pick_one_preserving_merges "$@" && return
|
||||||
@ -240,16 +240,7 @@ pick_one () {
|
|||||||
output git cherry-pick "$@"
|
output git cherry-pick "$@"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
parent_sha1=$(git rev-parse --verify $sha1^) ||
|
output git cherry-pick $ff "$@"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pick_one_preserving_merges () {
|
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;
|
*target = unset ? 2 : 1;
|
||||||
return 0;
|
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_end(struct parse_opt_ctx_t *ctx);
|
||||||
|
|
||||||
|
extern int parse_options_concat(struct option *dst, size_t, struct option *src);
|
||||||
|
|
||||||
/*----- some often used options -----*/
|
/*----- some often used options -----*/
|
||||||
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
|
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