Merge branch 'maint'
[gitweb.git] / builtin / revert.c
index f4ad0fc971bdbd5d372b74dbe4afbff07ddcc6f8..8b9d829a73a0862dfdf2c2499b18878489620f16 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,44 +36,59 @@ 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;
+static int commit_argc;
+static const char **commit_argv;
 static int allow_rerere_auto;
 
 static const char *me;
+static const char *strategy;
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
 static char *get_encoding(const char *message);
 
+static const char * const *revert_or_cherry_pick_usage(void)
+{
+       return action == REVERT ? revert_usage : cherry_pick_usage;
+}
+
 static void parse_args(int argc, const char **argv)
 {
-       const char * const * usage_str =
-               action == REVERT ?  revert_usage : cherry_pick_usage;
-       unsigned char sha1[20];
+       const char * const * usage_str = revert_or_cherry_pick_usage();
        int noop;
        struct option options[] = {
                OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
                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_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
                OPT_INTEGER('m', "mainline", &mainline, "parent number"),
                OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
+               OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
+               OPT_END(),
+               OPT_END(),
                OPT_END(),
        };
 
-       if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
+       if (action == CHERRY_PICK) {
+               struct option cp_extra[] = {
+                       OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
+                       OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+                       OPT_END(),
+               };
+               if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
+                       die("program error");
+       }
+
+       commit_argc = parse_options(argc, argv, NULL, options, usage_str,
+                                   PARSE_OPT_KEEP_ARGV0 |
+                                   PARSE_OPT_KEEP_UNKNOWN);
+       if (commit_argc < 2)
                usage_with_options(usage_str, options);
 
-       commit_name = argv[0];
-       if (get_sha1(commit_name, sha1))
-               die ("Cannot find '%s'", commit_name);
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               exit(1);
+       commit_argv = argv;
 }
 
 struct commit_message {
@@ -97,8 +113,13 @@ static int get_message(const char *raw_message, struct commit_message *out)
                encoding = "UTF-8";
        if (!git_commit_encoding)
                git_commit_encoding = "UTF-8";
-       if ((out->reencoded_message = reencode_string(raw_message,
-                                       git_commit_encoding, encoding)))
+
+       out->reencoded_message = NULL;
+       out->message = raw_message;
+       if (strcmp(encoding, git_commit_encoding))
+               out->reencoded_message = reencode_string(raw_message,
+                                       git_commit_encoding, encoding);
+       if (out->reencoded_message)
                out->message = out->reencoded_message;
 
        abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
@@ -220,7 +241,7 @@ static void set_author_ident_env(const char *message)
                        sha1_to_hex(commit->object.sha1));
 }
 
-static char *help_msg(const char *name)
+static char *help_msg(void)
 {
        struct strbuf helpbuf = STRBUF_INIT;
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
@@ -236,7 +257,7 @@ static char *help_msg(const char *name)
                strbuf_addf(&helpbuf, " with: \n"
                        "\n"
                        "        git commit -c %s\n",
-                       name);
+                           sha1_to_hex(commit->object.sha1));
        }
        else
                strbuf_addch(&helpbuf, '.');
@@ -279,6 +300,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 void do_recursive_merge(struct commit *base, struct commit *next,
                               const char *base_label, const char *next_label,
                               unsigned char *head, struct strbuf *msgbuf,
@@ -327,7 +359,7 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
                }
                write_message(msgbuf, defmsg);
                fprintf(stderr, "Automatic %s failed.%s\n",
-                       me, help_msg(commit_name));
+                       me, help_msg());
                rerere(allow_rerere_auto);
                exit(1);
        }
@@ -335,26 +367,15 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
        fprintf(stderr, "Finished one %s.\n", me);
 }
 
-static int revert_or_cherry_pick(int argc, const char **argv)
+static int do_pick_commit(void)
 {
        unsigned char head[20];
        struct commit *base, *next, *parent;
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
-       char *defmsg = git_pathdup("MERGE_MSG");
+       char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
 
-       git_config(git_default_config, NULL);
-       me = action == REVERT ? "revert" : "cherry-pick";
-       setenv(GIT_REFLOG_ACTION, me, 0);
-       parse_args(argc, argv);
-
-       /* this is copied from the shell script, but it's never triggered... */
-       if (action == REVERT && !no_replay)
-               die("revert is incompatible with replay");
-
-       if (read_cache() < 0)
-               die("git %s: failed to read the index", me);
        if (no_commit) {
                /*
                 * We do not intend to commit immediately.  We just want to
@@ -400,6 +421,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 (parent && parse_commit(parent) < 0)
                die("%s: cannot parse parent commit %s",
                    me, sha1_to_hex(parent->object.sha1));
@@ -415,6 +439,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
         * reverse of it if we are revert.
         */
 
+       defmsg = git_pathdup("MERGE_MSG");
+
        if (action == REVERT) {
                base = commit;
                base_label = msg.label;
@@ -444,8 +470,29 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                }
        }
 
-       do_recursive_merge(base, next, base_label, next_label,
-                          head, &msgbuf, defmsg);
+       if (!strategy || !strcmp(strategy, "recursive") || action == REVERT)
+               do_recursive_merge(base, next, base_label, next_label,
+                                  head, &msgbuf, defmsg);
+       else {
+               int res;
+               struct commit_list *common = NULL;
+               struct commit_list *remotes = NULL;
+               write_message(&msgbuf, defmsg);
+               commit_list_insert(base, &common);
+               commit_list_insert(next, &remotes);
+               res = try_merge_command(strategy, common,
+                                       sha1_to_hex(head), remotes);
+               free_commit_list(common);
+               free_commit_list(remotes);
+               if (res) {
+                       fprintf(stderr, "Automatic %s with strategy %s failed.%s\n",
+                               me, strategy, help_msg());
+                       rerere(allow_rerere_auto);
+                       exit(1);
+               }
+       }
+
+       free_message(&msg);
 
        /*
         *
@@ -459,7 +506,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        if (!no_commit) {
                /* 6 is max possible length of our args array including NULL */
                const char *args[6];
+               int res;
                int i = 0;
+
                args[i++] = "commit";
                args[i++] = "-n";
                if (signoff)
@@ -469,26 +518,81 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        args[i++] = defmsg;
                }
                args[i] = NULL;
-               return execv_git_cmd(args);
+               res = run_command_v_opt(args, RUN_GIT_CMD);
+               free(defmsg);
+
+               return res;
        }
-       free_message(&msg);
+
        free(defmsg);
 
        return 0;
 }
 
+static void prepare_revs(struct rev_info *revs)
+{
+       int argc;
+
+       init_revisions(revs, NULL);
+       revs->no_walk = 1;
+       if (action != REVERT)
+               revs->reverse = 1;
+
+       argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
+       if (argc > 1)
+               usage(*revert_or_cherry_pick_usage());
+
+       if (prepare_revision_walk(revs))
+               die("revision walk setup failed");
+
+       if (!revs->commits)
+               die("empty commit set passed");
+}
+
+static int revert_or_cherry_pick(int argc, const char **argv)
+{
+       struct rev_info revs;
+
+       git_config(git_default_config, NULL);
+       me = action == REVERT ? "revert" : "cherry-pick";
+       setenv(GIT_REFLOG_ACTION, me, 0);
+       parse_args(argc, argv);
+
+       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);
+
+       prepare_revs(&revs);
+
+       while ((commit = get_revision(&revs))) {
+               int res = do_pick_commit();
+               if (res)
+                       return res;
+       }
+
+       return 0;
+}
+
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
        if (isatty(0))
                edit = 1;
-       no_replay = 1;
        action = REVERT;
        return revert_or_cherry_pick(argc, argv);
 }
 
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
-       no_replay = 0;
        action = CHERRY_PICK;
        return revert_or_cherry_pick(argc, argv);
 }