Merge branch 'cc/cherry-pick-ff'
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 Mar 2010 04:52:28 +0000 (21:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Mar 2010 04:52:28 +0000 (21:52 -0700)
* 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

Documentation/git-cherry-pick.txt
builtin/merge.c
builtin/revert.c
cache.h
git-rebase--interactive.sh
parse-options.c
parse-options.h
t/t3506-cherry-pick-ff.sh [new file with mode: 0755]
index 78f4714da0c226b9523f60f247f60ab13119c7c7..d71607a85d03062c76f7d17d4fd8e8b4168a279d 100644 (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
 ------
index 3aaec7bed76af9efdfe5647be0da64373db2011e..c043066845e03e47093f88ca0b01c7bdef7bffdd 100644 (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;
index eff52687a87b45b766e064eede7c2c35b6d133d2..9a3c14c329713f16d14147b81a4b8031028a4f82 100644 (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, ' ');
 
diff --git a/cache.h b/cache.h
index 2928107bb135004e11f6a11a625067545680b62a..6dcb100a6cfc1b1d311602507bc3dcb483c1558d 100644 (file)
--- a/cache.h
+++ b/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 */
index 8a2771f1f9217750334c939edfcdb2b9d278ee96..1d116bfae2175891905d40ba4b55b331fba63372 100755 (executable)
@@ -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 () {
index c83035d013d3f50e9367f2b07d49cf17a1e22ff6..8546d8526f311e2a2703c258a4da3f02f8650df4 100644 (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;
+}
index 9429f7e36112b7b9cf52d4b3b68b007938396322..7581e931da13151473739036a89d9d19303eb18b 100644 (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);
diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
new file mode 100755 (executable)
index 0000000..e17ae71
--- /dev/null
@@ -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