Code

git-blame --incremental
[git.git] / merge-recursive.c
index 611cd95cf597b0ccf06cf9b4318eb34a86a4c05a..fa320eb6b1a9e257167e7f843fe36cbdfd908726 100644 (file)
@@ -3,13 +3,6 @@
  * Fredrik Kuivinen.
  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
  */
-#include <stdarg.h>
-#include <string.h>
-#include <assert.h>
-#include <sys/wait.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <time.h>
 #include "cache.h"
 #include "cache-tree.h"
 #include "commit.h"
@@ -21,6 +14,7 @@
 #include "tag.h"
 #include "unpack-trees.h"
 #include "path-list.h"
+#include "xdiff-interface.h"
 
 /*
  * A virtual commit has
@@ -73,32 +67,80 @@ struct stage_data
        unsigned processed:1;
 };
 
+struct output_buffer
+{
+       struct output_buffer *next;
+       char *str;
+};
+
 static struct path_list current_file_set = {NULL, 0, 0, 1};
 static struct path_list current_directory_set = {NULL, 0, 0, 1};
 
-static int output_indent = 0;
+static int call_depth = 0;
+static int verbosity = 2;
+static int buffer_output = 1;
+static int do_progress = 1;
+static unsigned last_percent;
+static unsigned merged_cnt;
+static unsigned total_cnt;
+static volatile sig_atomic_t progress_update;
+static struct output_buffer *output_list, *output_end;
+
+static int show (int v)
+{
+       return (!call_depth && verbosity >= v) || verbosity >= 5;
+}
 
-static void output(const char *fmt, ...)
+static void output(int v, const char *fmt, ...)
 {
        va_list args;
-       int i;
-       for (i = output_indent; i--;)
-               fputs("  ", stdout);
        va_start(args, fmt);
-       vfprintf(stdout, fmt, args);
+       if (buffer_output && show(v)) {
+               struct output_buffer *b = xmalloc(sizeof(*b));
+               nfvasprintf(&b->str, fmt, args);
+               b->next = NULL;
+               if (output_end)
+                       output_end->next = b;
+               else
+                       output_list = b;
+               output_end = b;
+       } else if (show(v)) {
+               int i;
+               for (i = call_depth; i--;)
+                       fputs("  ", stdout);
+               vfprintf(stdout, fmt, args);
+               fputc('\n', stdout);
+       }
        va_end(args);
-       fputc('\n', stdout);
+}
+
+static void flush_output()
+{
+       struct output_buffer *b, *n;
+       for (b = output_list; b; b = n) {
+               int i;
+               for (i = call_depth; i--;)
+                       fputs("  ", stdout);
+               fputs(b->str, stdout);
+               fputc('\n', stdout);
+               n = b->next;
+               free(b->str);
+               free(b);
+       }
+       output_list = NULL;
+       output_end = NULL;
 }
 
 static void output_commit_title(struct commit *commit)
 {
        int i;
-       for (i = output_indent; i--;)
+       flush_output();
+       for (i = call_depth; i--;)
                fputs("  ", stdout);
        if (commit->util)
                printf("virtual %s\n", (char *)commit->util);
        else {
-               printf("%s ", sha1_to_hex(commit->object.sha1));
+               printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
@@ -116,33 +158,37 @@ static void output_commit_title(struct commit *commit)
        }
 }
 
-static const char *current_index_file = NULL;
-static const char *original_index_file;
-static const char *temporary_index_file;
-static int cache_dirty = 0;
+static void progress_interval(int signum)
+{
+       progress_update = 1;
+}
 
-static int flush_cache(void)
+static void setup_progress_signal(void)
 {
-       /* flush temporary index */
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int fd = hold_lock_file_for_update(lock, current_index_file, 1);
-       if (write_cache(fd, active_cache, active_nr) ||
-                       close(fd) || commit_lock_file(lock))
-               die ("unable to write %s", current_index_file);
-       discard_cache();
-       cache_dirty = 0;
-       return 0;
+       struct sigaction sa;
+       struct itimerval v;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = progress_interval;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+
+       v.it_interval.tv_sec = 1;
+       v.it_interval.tv_usec = 0;
+       v.it_value = v.it_interval;
+       setitimer(ITIMER_REAL, &v, NULL);
 }
 
-static void setup_index(int temp)
+static void display_progress()
 {
-       current_index_file = temp ? temporary_index_file: original_index_file;
-       if (cache_dirty) {
-               discard_cache();
-               cache_dirty = 0;
+       unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0;
+       if (progress_update || percent != last_percent) {
+               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                       percent, merged_cnt, total_cnt);
+               progress_update = 0;
+               last_percent = percent;
        }
-       unlink(temporary_index_file);
-       discard_cache();
 }
 
 static struct cache_entry *make_cache_entry(unsigned int mode,
@@ -173,9 +219,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                const char *path, int stage, int refresh, int options)
 {
        struct cache_entry *ce;
-       if (!cache_dirty)
-               read_cache_from(current_index_file);
-       cache_dirty++;
        ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
        if (!ce)
                return error("cache_addinfo failed: %s", strerror(cache_errno));
@@ -193,26 +236,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
  */
 static int index_only = 0;
 
-static int git_read_tree(struct tree *tree)
-{
-       int rc;
-       struct object_list *trees = NULL;
-       struct unpack_trees_options opts;
-
-       if (cache_dirty)
-               die("read-tree with dirty cache");
-
-       memset(&opts, 0, sizeof(opts));
-       object_list_append(&tree->object, &trees);
-       rc = unpack_trees(trees, &opts);
-       cache_tree_free(&active_cache_tree);
-
-       if (rc == 0)
-               cache_dirty = 1;
-
-       return rc;
-}
-
 static int git_merge_trees(int index_only,
                           struct tree *common,
                           struct tree *head,
@@ -222,11 +245,6 @@ static int git_merge_trees(int index_only,
        struct object_list *trees = NULL;
        struct unpack_trees_options opts;
 
-       if (!cache_dirty) {
-               read_cache_from(current_index_file);
-               cache_dirty = 1;
-       }
-
        memset(&opts, 0, sizeof(opts));
        if (index_only)
                opts.index_only = 1;
@@ -242,39 +260,37 @@ static int git_merge_trees(int index_only,
 
        rc = unpack_trees(trees, &opts);
        cache_tree_free(&active_cache_tree);
-
-       cache_dirty = 1;
-
        return rc;
 }
 
+static int unmerged_index(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce))
+                       return 1;
+       }
+       return 0;
+}
+
 static struct tree *git_write_tree(void)
 {
        struct tree *result = NULL;
 
-       if (cache_dirty) {
-               unsigned i;
-               for (i = 0; i < active_nr; i++) {
-                       struct cache_entry *ce = active_cache[i];
-                       if (ce_stage(ce))
-                               return NULL;
-               }
-       } else
-               read_cache_from(current_index_file);
+       if (unmerged_index())
+               return NULL;
 
        if (!active_cache_tree)
                active_cache_tree = cache_tree();
 
        if (!cache_tree_fully_valid(active_cache_tree) &&
-                       cache_tree_update(active_cache_tree,
-                               active_cache, active_nr, 0, 0) < 0)
+           cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
 
-       flush_cache();
-       cache_dirty = 0;
-
        return result;
 }
 
@@ -337,14 +353,14 @@ static struct path_list *get_unmerged(void)
        int i;
 
        unmerged->strdup_paths = 1;
-       if (!cache_dirty) {
-               read_cache_from(current_index_file);
-               cache_dirty++;
-       }
-       for (i = 0; i < active_nr; i++) {
+       total_cnt += active_nr;
+
+       for (i = 0; i < active_nr; i++, merged_cnt++) {
                struct path_list_item *item;
                struct stage_data *e;
                struct cache_entry *ce = active_cache[i];
+               if (do_progress)
+                       display_progress();
                if (!ce_stage(ce))
                        continue;
 
@@ -427,8 +443,9 @@ static struct path_list *get_renames(struct tree *tree,
        return renames;
 }
 
-int update_stages(const char *path, struct diff_filespec *o,
-               struct diff_filespec *a, struct diff_filespec *b, int clear)
+static int update_stages(const char *path, struct diff_filespec *o,
+                        struct diff_filespec *a, struct diff_filespec *b,
+                        int clear)
 {
        int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
        if (clear)
@@ -468,20 +485,16 @@ static int remove_path(const char *name)
        return ret;
 }
 
-int remove_file(int clean, const char *path)
+static int remove_file(int clean, const char *path, int no_wd)
 {
        int update_cache = index_only || clean;
-       int update_working_directory = !index_only;
+       int update_working_directory = !index_only && !no_wd;
 
        if (update_cache) {
-               if (!cache_dirty)
-                       read_cache_from(current_index_file);
-               cache_dirty++;
                if (remove_file_from_cache(path))
                        return -1;
        }
-       if (update_working_directory)
-       {
+       if (update_working_directory) {
                unlink(path);
                if (errno != ENOENT || errno != EISDIR)
                        return -1;
@@ -523,7 +536,7 @@ static int mkdir_p(const char *path, unsigned long mode)
 static void flush_buffer(int fd, const char *buf, unsigned long size)
 {
        while (size > 0) {
-               long ret = xwrite(fd, buf, size);
+               long ret = write_in_full(fd, buf, size);
                if (ret < 0) {
                        /* Ignore epipe */
                        if (errno == EPIPE)
@@ -537,11 +550,11 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-void update_file_flags(const unsigned char *sha,
-                      unsigned mode,
-                      const char *path,
-                      int update_cache,
-                      int update_wd)
+static void update_file_flags(const unsigned char *sha,
+                             unsigned mode,
+                             const char *path,
+                             int update_cache,
+                             int update_wd)
 {
        if (index_only)
                update_wd = 0;
@@ -586,10 +599,10 @@ void update_file_flags(const unsigned char *sha,
                add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 }
 
-void update_file(int clean,
-               const unsigned char *sha,
-               unsigned mode,
-               const char *path)
+static void update_file(int clean,
+                       const unsigned char *sha,
+                       unsigned mode,
+                       const char *path)
 {
        update_file_flags(sha, mode, path, index_only || clean, !index_only);
 }
@@ -604,24 +617,21 @@ struct merge_file_info
                 merge:1;
 };
 
-static char *git_unpack_file(const unsigned char *sha1, char *path)
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 {
-       void *buf;
-       char type[20];
        unsigned long size;
-       int fd;
+       char type[20];
 
-       buf = read_sha1_file(sha1, type, &size);
-       if (!buf || strcmp(type, blob_type))
-               die("unable to read blob object %s", sha1_to_hex(sha1));
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
 
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = mkstemp(path);
-       if (fd < 0)
-               die("unable to create temp-file");
-       flush_buffer(fd, buf, size);
-       close(fd);
-       return path;
+       mm->ptr = read_sha1_file(sha1, type, &size);
+       if (!mm->ptr || strcmp(type, blob_type))
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
 }
 
 static struct merge_file_info merge_file(struct diff_filespec *o,
@@ -652,49 +662,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                else if (sha_eq(b->sha1, o->sha1))
                        hashcpy(result.sha, a->sha1);
                else if (S_ISREG(a->mode)) {
-                       int code = 1, fd;
-                       struct stat st;
-                       char orig[PATH_MAX];
-                       char src1[PATH_MAX];
-                       char src2[PATH_MAX];
-                       const char *argv[] = {
-                               "merge", "-L", NULL, "-L", NULL, "-L", NULL,
-                               NULL, NULL, NULL,
-                               NULL
-                       };
-                       char *la, *lb, *lo;
-
-                       git_unpack_file(o->sha1, orig);
-                       git_unpack_file(a->sha1, src1);
-                       git_unpack_file(b->sha1, src2);
-
-                       argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path));
-                       argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path));
-                       argv[4] = lo = xstrdup(mkpath("orig/%s", o->path));
-                       argv[7] = src1;
-                       argv[8] = orig;
-                       argv[9] = src2,
-
-                       code = run_command_v(10, argv);
-
-                       free(la);
-                       free(lb);
-                       free(lo);
-                       if (code && code < -256) {
-                               die("Failed to execute 'merge'. merge(1) is used as the "
-                                   "file-level merge tool. Is 'merge' in your path?");
-                       }
-                       fd = open(src1, O_RDONLY);
-                       if (fd < 0 || fstat(fd, &st) < 0 ||
-                                       index_fd(result.sha, fd, &st, 1,
-                                               "blob"))
-                               die("Unable to add %s to database", src1);
-
-                       unlink(orig);
-                       unlink(src1);
-                       unlink(src2);
-
-                       result.clean = WEXITSTATUS(code) == 0;
+                       mmfile_t orig, src1, src2;
+                       mmbuffer_t result_buf;
+                       xpparam_t xpp;
+                       char *name1, *name2;
+                       int merge_status;
+
+                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+
+                       fill_mm(o->sha1, &orig);
+                       fill_mm(a->sha1, &src1);
+                       fill_mm(b->sha1, &src2);
+
+                       memset(&xpp, 0, sizeof(xpp));
+                       merge_status = xdl_merge(&orig,
+                                                &src1, name1,
+                                                &src2, name2,
+                                                &xpp, XDL_MERGE_ZEALOUS,
+                                                &result_buf);
+                       free(name1);
+                       free(name2);
+                       free(orig.ptr);
+                       free(src1.ptr);
+                       free(src2.ptr);
+
+                       if ((merge_status < 0) || !result_buf.ptr)
+                               die("Failed to execute internal merge");
+
+                       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                                           blob_type, result.sha))
+                               die("Unable to add %s to database",
+                                   a->path);
+
+                       free(result_buf.ptr);
+                       result.clean = (merge_status == 0);
                } else {
                        if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode)))
                                die("cannot merge modes?");
@@ -722,15 +724,15 @@ static void conflict_rename_rename(struct rename *ren1,
        const char *dst_name2 = ren2_dst;
        if (path_list_has_path(&current_directory_set, ren1_dst)) {
                dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
-               output("%s is a directory in %s adding as %s instead",
+               output(1, "%s is a directory in %s added as %s instead",
                       ren1_dst, branch2, dst_name1);
-               remove_file(0, ren1_dst);
+               remove_file(0, ren1_dst, 0);
        }
        if (path_list_has_path(&current_directory_set, ren2_dst)) {
                dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
-               output("%s is a directory in %s adding as %s instead",
+               output(1, "%s is a directory in %s added as %s instead",
                       ren2_dst, branch1, dst_name2);
-               remove_file(0, ren2_dst);
+               remove_file(0, ren2_dst, 0);
        }
        update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
        update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
@@ -742,8 +744,8 @@ static void conflict_rename_dir(struct rename *ren1,
                                const char *branch1)
 {
        char *new_path = unique_path(ren1->pair->two->path, branch1);
-       output("Renaming %s to %s instead", ren1->pair->one->path, new_path);
-       remove_file(0, ren1->pair->two->path);
+       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
+       remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
        free(new_path);
 }
@@ -755,10 +757,10 @@ static void conflict_rename_rename_2(struct rename *ren1,
 {
        char *new_path1 = unique_path(ren1->pair->two->path, branch1);
        char *new_path2 = unique_path(ren2->pair->two->path, branch2);
-       output("Renaming %s to %s and %s to %s instead",
+       output(1, "Renamed %s to %s and %s to %s instead",
               ren1->pair->one->path, new_path1,
               ren2->pair->one->path, new_path2);
-       remove_file(0, ren1->pair->two->path);
+       remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
        update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
        free(new_path2);
@@ -848,7 +850,7 @@ static int process_renames(struct path_list *a_renames,
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/rename): "
+                               output(1, "CONFLICT (rename/rename): "
                                       "Rename %s->%s in branch %s "
                                       "rename %s->%s in %s",
                                       src, ren1_dst, branch1,
@@ -856,20 +858,20 @@ static int process_renames(struct path_list *a_renames,
                                conflict_rename_rename(ren1, branch1, ren2, branch2);
                        } else {
                                struct merge_file_info mfi;
-                               remove_file(1, ren1_src);
+                               remove_file(1, ren1_src, 1);
                                mfi = merge_file(ren1->pair->one,
                                                 ren1->pair->two,
                                                 ren2->pair->two,
                                                 branch1,
                                                 branch2);
                                if (mfi.merge || !mfi.clean)
-                                       output("Renaming %s->%s", src, ren1_dst);
+                                       output(1, "Renamed %s->%s", src, ren1_dst);
 
                                if (mfi.merge)
-                                       output("Auto-merging %s", ren1_dst);
+                                       output(2, "Auto-merged %s", ren1_dst);
 
                                if (!mfi.clean) {
-                                       output("CONFLICT (content): merge conflict in %s",
+                                       output(1, "CONFLICT (content): merge conflict in %s",
                                               ren1_dst);
                                        clean_merge = 0;
 
@@ -889,7 +891,7 @@ static int process_renames(struct path_list *a_renames,
                        struct diff_filespec src_other, dst_other;
                        int try_merge, stage = a_renames == renames1 ? 3: 2;
 
-                       remove_file(1, ren1_src);
+                       remove_file(1, ren1_src, index_only);
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
                        src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -900,14 +902,14 @@ static int process_renames(struct path_list *a_renames,
 
                        if (path_list_has_path(&current_directory_set, ren1_dst)) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/directory): Rename %s->%s in %s "
+                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
                                       " directory %s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                conflict_rename_dir(ren1, branch1);
                        } else if (sha_eq(src_other.sha1, null_sha1)) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/delete): Rename %s->%s in %s "
+                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
                                       "and deleted in %s",
                                       ren1_src, ren1_dst, branch1,
                                       branch2);
@@ -916,19 +918,19 @@ static int process_renames(struct path_list *a_renames,
                                const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
-                               output("CONFLICT (rename/add): Rename %s->%s in %s. "
+                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
                                       "%s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                new_path = unique_path(ren1_dst, branch2);
-                               output("Adding as %s instead", new_path);
+                               output(1, "Added as %s instead", new_path);
                                update_file(0, dst_other.sha1, dst_other.mode, new_path);
                        } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
-                               output("CONFLICT (rename/rename): Rename %s->%s in %s. "
-                                      "Rename %s->%s in %s",
+                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
+                                      "Renamed %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
                                conflict_rename_rename_2(ren1, branch1, ren2, branch2);
@@ -952,11 +954,11 @@ static int process_renames(struct path_list *a_renames,
                                                a_branch, b_branch);
 
                                if (mfi.merge || !mfi.clean)
-                                       output("Renaming %s => %s", ren1_src, ren1_dst);
+                                       output(1, "Renamed %s => %s", ren1_src, ren1_dst);
                                if (mfi.merge)
-                                       output("Auto-merging %s", ren1_dst);
+                                       output(2, "Auto-merged %s", ren1_dst);
                                if (!mfi.clean) {
-                                       output("CONFLICT (rename/modify): Merge conflict in %s",
+                                       output(1, "CONFLICT (rename/modify): Merge conflict in %s",
                                               ren1_dst);
                                        clean_merge = 0;
 
@@ -971,8 +973,6 @@ static int process_renames(struct path_list *a_renames,
        path_list_clear(&a_by_dst, 0);
        path_list_clear(&b_by_dst, 0);
 
-       if (cache_dirty)
-               flush_cache();
        return clean_merge;
 }
 
@@ -1006,19 +1006,20 @@ static int process_entry(const char *path, struct stage_data *entry,
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
-                               output("Removing %s", path);
-                       remove_file(1, path);
+                               output(2, "Removed %s", path);
+                       /* do not touch working file if it did not exist */
+                       remove_file(1, path, !a_sha);
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
                        if (!a_sha) {
-                               output("CONFLICT (delete/modify): %s deleted in %s "
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
                                       "and modified in %s. Version %s of %s left in tree.",
                                       path, branch1,
                                       branch2, branch2, path);
                                update_file(0, b_sha, b_mode, path);
                        } else {
-                               output("CONFLICT (delete/modify): %s deleted in %s "
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
                                       "and modified in %s. Version %s of %s left in tree.",
                                       path, branch2,
                                       branch1, branch1, path);
@@ -1051,48 +1052,27 @@ static int process_entry(const char *path, struct stage_data *entry,
                if (path_list_has_path(&current_directory_set, path)) {
                        const char *new_path = unique_path(path, add_branch);
                        clean_merge = 0;
-                       output("CONFLICT (%s): There is a directory with name %s in %s. "
-                              "Adding %s as %s",
+                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                              "Added %s as %s",
                               conf, path, other_branch, path, new_path);
-                       remove_file(0, path);
+                       remove_file(0, path, 0);
                        update_file(0, sha, mode, new_path);
                } else {
-                       output("Adding %s", path);
+                       output(2, "Added %s", path);
                        update_file(1, sha, mode, path);
                }
-       } else if (!o_sha && a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions). */
-               if (sha_eq(a_sha, b_sha)) {
-                       if (a_mode != b_mode) {
-                               clean_merge = 0;
-                               output("CONFLICT: File %s added identically in both branches, "
-                                      "but permissions conflict %06o->%06o",
-                                      path, a_mode, b_mode);
-                               output("CONFLICT: adding with permission: %06o", a_mode);
-                               update_file(0, a_sha, a_mode, path);
-                       } else {
-                               /* This case is handled by git-read-tree */
-                               assert(0 && "This case must be handled by git-read-tree");
-                       }
-               } else {
-                       const char *new_path1, *new_path2;
-                       clean_merge = 0;
-                       new_path1 = unique_path(path, branch1);
-                       new_path2 = unique_path(path, branch2);
-                       output("CONFLICT (add/add): File %s added non-identically "
-                              "in both branches. Adding as %s and %s instead.",
-                              path, new_path1, new_path2);
-                       remove_file(0, path);
-                       update_file(0, a_sha, a_mode, new_path1);
-                       update_file(0, b_sha, b_mode, new_path2);
-               }
-
-       } else if (o_sha && a_sha && b_sha) {
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
+               const char *reason = "content";
                struct merge_file_info mfi;
                struct diff_filespec o, a, b;
 
-               output("Auto-merging %s", path);
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
+               output(2, "Auto-merged %s", path);
                o.path = a.path = b.path = (char *)path;
                hashcpy(o.sha1, o_sha);
                o.mode = o_mode;
@@ -1108,7 +1088,8 @@ static int process_entry(const char *path, struct stage_data *entry,
                        update_file(1, mfi.sha, mfi.mode, path);
                else {
                        clean_merge = 0;
-                       output("CONFLICT (content): Merge conflict in %s", path);
+                       output(1, "CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
 
                        if (index_only)
                                update_file(0, mfi.sha, mfi.mode, path);
@@ -1119,9 +1100,6 @@ static int process_entry(const char *path, struct stage_data *entry,
        } else
                die("Fatal merge failure, shouldn't happen.");
 
-       if (cache_dirty)
-               flush_cache();
-
        return clean_merge;
 }
 
@@ -1134,7 +1112,7 @@ static int merge_trees(struct tree *head,
 {
        int code, clean;
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
-               output("Already uptodate!");
+               output(0, "Already uptodate!");
                *result = head;
                return 1;
        }
@@ -1146,9 +1124,7 @@ static int merge_trees(struct tree *head,
                    sha1_to_hex(head->object.sha1),
                    sha1_to_hex(merge->object.sha1));
 
-       *result = git_write_tree();
-
-       if (!*result) {
+       if (unmerged_index()) {
                struct path_list *entries, *re_head, *re_merge;
                int i;
                path_list_clear(&current_file_set, 1);
@@ -1161,30 +1137,27 @@ static int merge_trees(struct tree *head,
                re_merge = get_renames(merge, common, head, merge, entries);
                clean = process_renames(re_head, re_merge,
                                branch1, branch2);
-               for (i = 0; i < entries->nr; i++) {
+               total_cnt += entries->nr;
+               for (i = 0; i < entries->nr; i++, merged_cnt++) {
                        const char *path = entries->items[i].path;
                        struct stage_data *e = entries->items[i].util;
-                       if (e->processed)
-                               continue;
-                       if (!process_entry(path, e, branch1, branch2))
+                       if (!e->processed
+                               && !process_entry(path, e, branch1, branch2))
                                clean = 0;
+                       if (do_progress)
+                               display_progress();
                }
 
                path_list_clear(re_merge, 0);
                path_list_clear(re_head, 0);
                path_list_clear(entries, 1);
 
-               if (clean || index_only)
-                       *result = git_write_tree();
-               else
-                       *result = NULL;
-       } else {
-               clean = 1;
-               printf("merging of trees %s and %s resulted in %s\n",
-                      sha1_to_hex(head->object.sha1),
-                      sha1_to_hex(merge->object.sha1),
-                      sha1_to_hex((*result)->object.sha1));
        }
+       else
+               clean = 1;
+
+       if (index_only)
+               *result = git_write_tree();
 
        return clean;
 }
@@ -1204,49 +1177,48 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
  * Merge the commits h1 and h2, return the resulting virtual
  * commit object and a flag indicating the cleaness of the merge.
  */
-static
-int merge(struct commit *h1,
-                         struct commit *h2,
-                         const char *branch1,
-                         const char *branch2,
-                         int call_depth /* =0 */,
-                         struct commit *ancestor /* =None */,
-                         struct commit **result)
+static int merge(struct commit *h1,
+                struct commit *h2,
+                const char *branch1,
+                const char *branch2,
+                struct commit_list *ca,
+                struct commit **result)
 {
-       struct commit_list *ca = NULL, *iter;
+       struct commit_list *iter;
        struct commit *merged_common_ancestors;
        struct tree *mrtree;
        int clean;
 
-       output("Merging:");
-       output_commit_title(h1);
-       output_commit_title(h2);
+       if (show(4)) {
+               output(4, "Merging:");
+               output_commit_title(h1);
+               output_commit_title(h2);
+       }
 
-       if (ancestor)
-               commit_list_insert(ancestor, &ca);
-       else
-               ca = reverse_commit_list(get_merge_bases(h1, h2, 1));
+       if (!ca) {
+               ca = get_merge_bases(h1, h2, 1);
+               ca = reverse_commit_list(ca);
+       }
 
-       output("found %u common ancestor(s):", commit_list_count(ca));
-       for (iter = ca; iter; iter = iter->next)
-               output_commit_title(iter->item);
+       if (show(5)) {
+               output(5, "found %u common ancestor(s):", commit_list_count(ca));
+               for (iter = ca; iter; iter = iter->next)
+                       output_commit_title(iter->item);
+       }
 
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
                /* if there is no common ancestor, make an empty tree */
                struct tree *tree = xcalloc(1, sizeof(struct tree));
-               unsigned char hdr[40];
-               int hdrlen;
 
                tree->object.parsed = 1;
                tree->object.type = OBJ_TREE;
-               write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1,
-                                       hdr, &hdrlen);
+               write_sha1_file(NULL, 0, tree_type, tree->object.sha1);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
        for (iter = ca; iter; iter = iter->next) {
-               output_indent = call_depth + 1;
+               call_depth++;
                /*
                 * When the merge fails, the result contains files
                 * with conflict markers. The cleanness flag is
@@ -1254,40 +1226,58 @@ int merge(struct commit *h1,
                 * merge_trees has always overwritten it: the commited
                 * "conflicts" were already resolved.
                 */
+               discard_cache();
                merge(merged_common_ancestors, iter->item,
                      "Temporary merge branch 1",
                      "Temporary merge branch 2",
-                     call_depth + 1,
                      NULL,
                      &merged_common_ancestors);
-               output_indent = call_depth;
+               call_depth--;
 
                if (!merged_common_ancestors)
                        die("merge returned no commit");
        }
 
-       if (call_depth == 0) {
-               setup_index(0 /* $GIT_DIR/index */);
+       discard_cache();
+       if (!call_depth) {
+               read_cache();
                index_only = 0;
-       } else {
-               setup_index(1 /* temporary index */);
-               git_read_tree(h1->tree);
+       } else
                index_only = 1;
-       }
 
        clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
                            branch1, branch2, &mrtree);
 
-       if (!ancestor && (clean || index_only)) {
+       if (index_only) {
                *result = make_virtual_commit(mrtree, "merged tree");
                commit_list_insert(h1, &(*result)->parents);
                commit_list_insert(h2, &(*result)->parents->next);
-       } else
-               *result = NULL;
-
+       }
+       if (!call_depth && do_progress) {
+               /* Make sure we end at 100% */
+               if (!total_cnt)
+                       total_cnt = 1;
+               merged_cnt = total_cnt;
+               progress_update = 1;
+               display_progress();
+               fputc('\n', stderr);
+       }
+       flush_output();
        return clean;
 }
 
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
 static struct commit *get_ref(const char *ref)
 {
        unsigned char sha1[20];
@@ -1296,6 +1286,9 @@ static struct commit *get_ref(const char *ref)
        if (get_sha1(ref, sha1))
                die("Could not resolve ref '%s'", ref);
        object = deref_tag(parse_object(sha1), ref, strlen(ref));
+       if (object->type == OBJ_TREE)
+               return make_virtual_commit((struct tree*)object,
+                       better_branch_name(ref));
        if (object->type != OBJ_COMMIT)
                return NULL;
        if (parse_commit((struct commit *)object))
@@ -1303,20 +1296,29 @@ static struct commit *get_ref(const char *ref)
        return (struct commit *)object;
 }
 
+static int merge_config(const char *var, const char *value)
+{
+       if (!strcasecmp(var, "merge.verbosity")) {
+               verbosity = git_config_int(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int main(int argc, char *argv[])
 {
-       static const char *bases[2];
+       static const char *bases[20];
        static unsigned bases_count = 0;
        int i, clean;
        const char *branch1, *branch2;
        struct commit *result, *h1, *h2;
+       struct commit_list *ca = NULL;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int index_fd;
 
-       original_index_file = getenv("GIT_INDEX_FILE");
-
-       if (!original_index_file)
-               original_index_file = xstrdup(git_path("index"));
-
-       temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx"));
+       git_config(merge_config);
+       if (getenv("GIT_MERGE_VERBOSITY"))
+               verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
 
        if (argc < 4)
                die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
@@ -1329,26 +1331,39 @@ int main(int argc, char *argv[])
        }
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die("Not handling anything other than two heads merge.");
+       if (verbosity >= 5) {
+               buffer_output = 0;
+               do_progress = 0;
+       }
+       else
+               do_progress = isatty(1);
 
        branch1 = argv[++i];
        branch2 = argv[++i];
-       printf("Merging %s with %s\n", branch1, branch2);
 
        h1 = get_ref(branch1);
        h2 = get_ref(branch2);
 
-       if (bases_count == 1) {
-               struct commit *ancestor = get_ref(bases[0]);
-               clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result);
-       } else
-               clean = merge(h1, h2, branch1, branch2, 0, NULL, &result);
+       branch1 = better_branch_name(branch1);
+       branch2 = better_branch_name(branch2);
+
+       if (do_progress)
+               setup_progress_signal();
+       if (show(3))
+               printf("Merging %s with %s\n", branch1, branch2);
 
-       if (cache_dirty)
-               flush_cache();
+       index_fd = hold_lock_file_for_update(lock, get_index_file(), 1);
+
+       for (i = 0; i < bases_count; i++) {
+               struct commit *ancestor = get_ref(bases[i]);
+               ca = commit_list_insert(ancestor, &ca);
+       }
+       clean = merge(h1, h2, branch1, branch2, ca, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            close(index_fd) || commit_lock_file(lock)))
+                       die ("unable to write %s", get_index_file());
 
        return clean ? 0: 1;
 }
-
-/*
-vim: sw=8 noet
-*/