From 7791ecbc62b792b3eaa6d722b6dadcea4d0f322d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 23 Oct 2007 13:33:26 -0700 Subject: [PATCH] revert/cherry-pick: work on merge commits as well Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline (iow, what change to reverse). With this patch, cherry-pick and revert learn -m (--mainline) option that lets you specify the parent number (starting from 1) of the mainline, so that you can: git revert -m 1 $merge to reverse the changes introduced by the $merge commit relative to its first parent, and: git cherry-pick -m 2 $merge to replay the changes introduced by the $merge commit relative to its second parent. Signed-off-by: Junio C Hamano --- Documentation/git-cherry-pick.txt | 9 ++++++- Documentation/git-revert.txt | 9 ++++++- builtin-revert.c | 42 +++++++++++++++++++++++++------ git-compat-util.h | 13 ++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 76a2edfd9b..937c4a7926 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit SYNOPSIS -------- -'git-cherry-pick' [--edit] [-n] [-x] +'git-cherry-pick' [--edit] [-n] [-m parent-number] [-x] 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 diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 69db498447..3457c40787 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -7,7 +7,7 @@ git-revert - Revert an existing commit SYNOPSIS -------- -'git-revert' [--edit | --no-edit] [-n] +'git-revert' [--edit | --no-edit] [-n] [-m parent-number] 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. diff --git a/builtin-revert.c b/builtin-revert.c index a655c8ee2a..bfed69d7e2 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -19,9 +19,9 @@ * Copyright (c) 2005 Junio C Hamano */ -static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] "; +static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] [-m parent-number] "; -static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-r] [-x] "; +static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-m parent-number] [-r] [-x] "; static int edit; static int replay; @@ -29,6 +29,7 @@ static enum { REVERT, CHERRY_PICK } action; static int no_commit; static struct commit *commit; static int needed_deref; +static int mainline; static const char *me; @@ -58,6 +59,12 @@ static void parse_options(int argc, const char **argv) else if (!strcmp(arg, "-x") || !strcmp(arg, "--i-really-want-" "to-expose-my-private-commit-object-name")) replay = 0; + else if (!strcmp(arg, "-m") || !strcmp(arg, "--mainline")) { + if (++i >= argc || + strtol_i(argv[i], 10, &mainline) || + mainline <= 0) + usage(usage_str); + } else if (strcmp(arg, "-r")) usage(usage_str); } @@ -234,7 +241,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; @@ -269,8 +276,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)); @@ -299,14 +327,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); diff --git a/git-compat-util.h b/git-compat-util.h index 474f1d1ffb..7b29d1b905 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -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