Merge branch 'jc/revert-merge'
authorJunio C Hamano <gitster@pobox.com>
Sun, 4 Nov 2007 08:26:02 +0000 (01:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 4 Nov 2007 08:26:02 +0000 (01:26 -0700)
* jc/revert-merge:
cherry-pick/revert -m: add tests
revert/cherry-pick: work on merge commits as well

Conflicts:

builtin-revert.c

Documentation/git-cherry-pick.txt
Documentation/git-revert.txt
builtin-revert.c
git-compat-util.h
t/t3502-cherry-pick-merge.sh [new file with mode: 0755]
index 76a2edfd9b3665b4dbe8e3837c06737e7c5fd232..937c4a79262d44583849fc81319187fa7fb65579 100644 (file)
@@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit
 
 SYNOPSIS
 --------
-'git-cherry-pick' [--edit] [-n] [-x] <commit>
+'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] <commit>
 
 DESCRIPTION
 -----------
@@ -44,6 +44,13 @@ OPTIONS
        described above, and `-r` was to disable it.  Now the
        default is not to do `-x` so this option is a no-op.
 
+-m parent-number|--mainline parent-number::
+       Usually you cannot revert a merge because you do not know which
+       side of the merge should be considered the mainline.  This
+       option specifies the parent number (starting from 1) of
+       the mainline and allows cherry-pick to replay the change
+       relative to the specified parent.
+
 -n|--no-commit::
        Usually the command automatically creates a commit with
        a commit log message stating which commit was
index 69db4984473fae7928797bcfb13baf1f0e39d851..3457c4078781d15f9b292d807119f28eee46866c 100644 (file)
@@ -7,7 +7,7 @@ git-revert - Revert an existing commit
 
 SYNOPSIS
 --------
-'git-revert' [--edit | --no-edit] [-n] <commit>
+'git-revert' [--edit | --no-edit] [-n] [-m parent-number] <commit>
 
 DESCRIPTION
 -----------
@@ -27,6 +27,13 @@ OPTIONS
        message prior committing the revert. This is the default if
        you run the command from a terminal.
 
+-m parent-number|--mainline parent-number::
+       Usually you cannot revert a merge because you do not know which
+       side of the merge should be considered the mainline.  This
+       option specifies the parent number (starting from 1) of
+       the mainline and allows revert to reverse the change
+       relative to the specified parent.
+
 --no-edit::
        With this option, `git-revert` will not start the commit
        message editor.
index a9347cf9c57b2e4d15d37da609a2851e5b1cfc8e..62ab1fa1f484a113f45da96de8cd209a0c5c522e 100644 (file)
@@ -30,7 +30,7 @@ static const char * const cherry_pick_usage[] = {
        NULL
 };
 
-static int edit, no_replay, no_commit, needed_deref;
+static int edit, no_replay, no_commit, needed_deref, mainline;
 static enum { REVERT, CHERRY_PICK } action;
 static struct commit *commit;
 
@@ -50,12 +50,14 @@ static void parse_args(int argc, const char **argv)
                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_INTEGER('m', "mainline", &mainline, "parent number"),
                OPT_END(),
        };
 
        if (parse_options(argc, argv, options, usage_str, 0) != 1)
                usage_with_options(usage_str, options);
        arg = argv[0];
+
        if (get_sha1(arg, sha1))
                die ("Cannot find '%s'", arg);
        commit = (struct commit *)parse_object(sha1);
@@ -226,7 +228,7 @@ static int merge_recursive(const char *base_sha1,
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
        unsigned char head[20];
-       struct commit *base, *next;
+       struct commit *base, *next, *parent;
        int i;
        char *oneline, *reencoded_message = NULL;
        const char *message, *encoding;
@@ -261,8 +263,29 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 
        if (!commit->parents)
                die ("Cannot %s a root commit", me);
-       if (commit->parents->next)
-               die ("Cannot %s a multi-parent commit.", me);
+       if (commit->parents->next) {
+               /* Reverting or cherry-picking a merge commit */
+               int cnt;
+               struct commit_list *p;
+
+               if (!mainline)
+                       die("Commit %s is a merge but no -m option was given.",
+                           sha1_to_hex(commit->object.sha1));
+
+               for (cnt = 1, p = commit->parents;
+                    cnt != mainline && p;
+                    cnt++)
+                       p = p->next;
+               if (cnt != mainline || !p)
+                       die("Commit %s does not have parent %d",
+                           sha1_to_hex(commit->object.sha1), mainline);
+               parent = p->item;
+       } else if (0 < mainline)
+               die("Mainline was specified but commit %s is not a merge.",
+                   sha1_to_hex(commit->object.sha1));
+       else
+               parent = commit->parents->item;
+
        if (!(message = commit->buffer))
                die ("Cannot get commit message for %s",
                                sha1_to_hex(commit->object.sha1));
@@ -291,14 +314,14 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                char *oneline_body = strchr(oneline, ' ');
 
                base = commit;
-               next = commit->parents->item;
+               next = parent;
                add_to_msg("Revert \"");
                add_to_msg(oneline_body + 1);
                add_to_msg("\"\n\nThis reverts commit ");
                add_to_msg(sha1_to_hex(commit->object.sha1));
                add_to_msg(".\n");
        } else {
-               base = commit->parents->item;
+               base = parent;
                next = commit;
                set_author_ident_env(message);
                add_message_to_msg(message);
index 474f1d1ffbee5433ec311174ee37804ab16417bb..7b29d1b905c618a50704a7b2c38041f71c942e71 100644 (file)
@@ -381,4 +381,17 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
        return 0;
 }
 
+static inline int strtol_i(char const *s, int base, int *result)
+{
+       long ul;
+       char *p;
+
+       errno = 0;
+       ul = strtol(s, &p, base);
+       if (errno || *p || p == s || (int) ul != ul)
+               return -1;
+       *result = ul;
+       return 0;
+}
+
 #endif
diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh
new file mode 100755 (executable)
index 0000000..3274c61
--- /dev/null
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+test_description='cherry picking and reverting a merge
+
+               b---c
+              /   /
+       initial---a
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >A &&
+       >B &&
+       git add A B &&
+       git commit -m "Initial" &&
+       git tag initial &&
+       git branch side &&
+       echo new line >A &&
+       git commit -m "add line to A" A &&
+       git tag a &&
+       git checkout side &&
+       echo new line >B &&
+       git commit -m "add line to B" B &&
+       git tag b &&
+       git checkout master &&
+       git merge side &&
+       git tag c
+
+'
+
+test_expect_success 'cherry-pick a non-merge with -m should fail' '
+
+       git reset --hard &&
+       git checkout a^0 &&
+       ! git cherry-pick -m 1 b &&
+       git diff --exit-code a
+
+'
+
+test_expect_success 'cherry pick a merge without -m should fail' '
+
+       git reset --hard &&
+       git checkout a^0 &&
+       ! git cherry-pick c &&
+       git diff --exit-code a
+
+'
+
+test_expect_success 'cherry pick a merge (1)' '
+
+       git reset --hard &&
+       git checkout a^0 &&
+       git cherry-pick -m 1 c &&
+       git diff --exit-code c
+
+'
+
+test_expect_success 'cherry pick a merge (2)' '
+
+       git reset --hard &&
+       git checkout b^0 &&
+       git cherry-pick -m 2 c &&
+       git diff --exit-code c
+
+'
+
+test_expect_success 'cherry pick a merge relative to nonexistent parent should fail' '
+
+       git reset --hard &&
+       git checkout b^0 &&
+       ! git cherry-pick -m 3 c
+
+'
+
+test_expect_success 'revert a non-merge with -m should fail' '
+
+       git reset --hard &&
+       git checkout c^0 &&
+       ! git revert -m 1 b &&
+       git diff --exit-code c
+
+'
+
+test_expect_success 'revert a merge without -m should fail' '
+
+       git reset --hard &&
+       git checkout c^0 &&
+       ! git revert c &&
+       git diff --exit-code c
+
+'
+
+test_expect_success 'revert a merge (1)' '
+
+       git reset --hard &&
+       git checkout c^0 &&
+       git revert -m 1 c &&
+       git diff --exit-code a
+
+'
+
+test_expect_success 'revert a merge (2)' '
+
+       git reset --hard &&
+       git checkout c^0 &&
+       git revert -m 2 c &&
+       git diff --exit-code b
+
+'
+
+test_expect_success 'revert a merge relative to nonexistent parent should fail' '
+
+       git reset --hard &&
+       git checkout c^0 &&
+       ! git revert -m 3 c &&
+       git diff --exit-code c
+
+'
+
+test_done