contrib: add 'git difftool' for launching common merge tools
[gitweb.git] / builtin-revert.c
index bde28b2c4df9e0d79589a594a8d93103a4f7bd39..d48313c7453c24e7ab4cbb3e024d05ec311edf7b 100644 (file)
@@ -11,6 +11,8 @@
 #include "cache-tree.h"
 #include "diff.h"
 #include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
  */
 
 static const char * const revert_usage[] = {
-       "git-revert [options] <commit-ish>",
+       "git revert [options] <commit-ish>",
        NULL
 };
 
 static const char * const cherry_pick_usage[] = {
-       "git-cherry-pick [options] <commit-ish>",
+       "git cherry-pick [options] <commit-ish>",
        NULL
 };
 
@@ -200,34 +202,6 @@ static void set_author_ident_env(const char *message)
                        sha1_to_hex(commit->object.sha1));
 }
 
-static int merge_recursive(const char *base_sha1,
-               const char *head_sha1, const char *head_name,
-               const char *next_sha1, const char *next_name)
-{
-       char buffer[256];
-       const char *argv[6];
-
-       sprintf(buffer, "GITHEAD_%s", head_sha1);
-       setenv(buffer, head_name, 1);
-       sprintf(buffer, "GITHEAD_%s", next_sha1);
-       setenv(buffer, next_name, 1);
-
-       /*
-        * This three way merge is an interesting one.  We are at
-        * $head, and would want to apply the change between $commit
-        * and $prev on top of us (when reverting), or the change between
-        * $prev and $commit on top of us (when cherry-picking or replaying).
-        */
-       argv[0] = "merge-recursive";
-       argv[1] = base_sha1;
-       argv[2] = "--";
-       argv[3] = head_sha1;
-       argv[4] = next_sha1;
-       argv[5] = NULL;
-
-       return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
-}
-
 static char *help_msg(const unsigned char *sha1)
 {
        static char helpbuf[1024];
@@ -260,14 +234,27 @@ static int index_is_dirty(void)
        return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
 }
 
+static struct tree *empty_tree(void)
+{
+       struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+       tree->object.parsed = 1;
+       tree->object.type = OBJ_TREE;
+       pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+       return tree;
+}
+
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
        unsigned char head[20];
        struct commit *base, *next, *parent;
-       int i;
+       int i, index_fd, clean;
        char *oneline, *reencoded_message = NULL;
        const char *message, *encoding;
-       const char *defmsg = xstrdup(git_path("MERGE_MSG"));
+       char *defmsg = git_pathdup("MERGE_MSG");
+       struct merge_options o;
+       struct tree *result, *next_tree, *base_tree, *head_tree;
+       static struct lock_file index_lock;
 
        git_config(git_default_config, NULL);
        me = action == REVERT ? "revert" : "cherry-pick";
@@ -278,6 +265,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        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
@@ -290,16 +279,19 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        } else {
                if (get_sha1("HEAD", head))
                        die ("You do not have a valid HEAD");
-               if (read_cache() < 0)
-                       die("could not read the index");
                if (index_is_dirty())
                        die ("Dirty index: cannot %s", me);
-               discard_cache();
        }
+       discard_cache();
+
+       index_fd = hold_locked_index(&index_lock, 1);
 
-       if (!commit->parents)
-               die ("Cannot %s a root commit", me);
-       if (commit->parents->next) {
+       if (!commit->parents) {
+               if (action == REVERT)
+                       die ("Cannot revert a root commit");
+               parent = NULL;
+       }
+       else if (commit->parents->next) {
                /* Reverting or cherry-picking a merge commit */
                int cnt;
                struct commit_list *p;
@@ -326,6 +318,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                die ("Cannot get commit message for %s",
                                sha1_to_hex(commit->object.sha1));
 
+       if (parent && parse_commit(parent) < 0)
+               die("%s: cannot parse parent commit %s",
+                   me, sha1_to_hex(parent->object.sha1));
+
        /*
         * "commit" is an existing commit.  We would want to apply
         * the difference it introduces since its first parent "prev"
@@ -333,7 +329,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
         * reverse of it if we are revert.
         */
 
-       msg_fd = hold_lock_file_for_update(&msg_file, defmsg, 1);
+       msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
+                                          LOCK_DIE_ON_ERROR);
 
        encoding = get_encoding(message);
        if (!encoding)
@@ -355,6 +352,11 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                add_to_msg(oneline_body + 1);
                add_to_msg("\"\n\nThis reverts commit ");
                add_to_msg(sha1_to_hex(commit->object.sha1));
+
+               if (commit->parents->next) {
+                       add_to_msg(", reversing\nchanges made to ");
+                       add_to_msg(sha1_to_hex(parent->object.sha1));
+               }
                add_to_msg(".\n");
        } else {
                base = parent;
@@ -368,12 +370,26 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                }
        }
 
-       if (merge_recursive(sha1_to_hex(base->object.sha1),
-                               sha1_to_hex(head), "HEAD",
-                               sha1_to_hex(next->object.sha1), oneline) ||
-                       write_cache_as_tree(head, 0, NULL)) {
+       read_cache();
+       init_merge_options(&o);
+       o.branch1 = "HEAD";
+       o.branch2 = oneline;
+
+       head_tree = parse_tree_indirect(head);
+       next_tree = next ? next->tree : empty_tree();
+       base_tree = base ? base->tree : empty_tree();
+
+       clean = merge_trees(&o,
+                           head_tree,
+                           next_tree, base_tree, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            commit_locked_index(&index_lock)))
+               die("%s: Unable to write new index file", me);
+
+       if (!clean) {
                add_to_msg("\nConflicts:\n\n");
-               read_cache();
                for (i = 0; i < active_nr;) {
                        struct cache_entry *ce = active_cache[i++];
                        if (ce_stage(ce)) {
@@ -389,6 +405,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        die ("Error wrapping up %s", defmsg);
                fprintf(stderr, "Automatic %s failed.%s\n",
                        me, help_msg(commit->object.sha1));
+               rerere();
                exit(1);
        }
        if (commit_lock_file(&msg_file) < 0)
@@ -420,6 +437,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                return execv_git_cmd(args);
        }
        free(reencoded_message);
+       free(defmsg);
 
        return 0;
 }