merge-recursive: add/add really is modify/modify with an empty base
[gitweb.git] / merge-recursive.c
index 611cd95cf597b0ccf06cf9b4318eb34a86a4c05a..58f2cb4ed3e3cb9450ee1aaf4219f3c337342d02 100644 (file)
@@ -21,6 +21,7 @@
 #include "tag.h"
 #include "unpack-trees.h"
 #include "path-list.h"
+#include "xdiff-interface.h"
 
 /*
  * A virtual commit has
@@ -98,7 +99,7 @@ static void output_commit_title(struct commit *commit)
        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 {
@@ -427,8 +428,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,10 +470,10 @@ 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)
@@ -480,8 +482,7 @@ int remove_file(int clean, const char *path)
                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;
@@ -537,11 +538,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 +587,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 +605,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 +650,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?");
@@ -724,13 +714,13 @@ static void conflict_rename_rename(struct rename *ren1,
                dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
                output("%s is a directory in %s adding 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",
                       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);
@@ -743,7 +733,7 @@ static void conflict_rename_dir(struct rename *ren1,
 {
        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);
+       remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
        free(new_path);
 }
@@ -758,7 +748,7 @@ static void conflict_rename_rename_2(struct rename *ren1,
        output("Renaming %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);
@@ -856,7 +846,7 @@ 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,
@@ -889,7 +879,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, 1);
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
                        src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -1007,7 +997,8 @@ static int process_entry(const char *path, struct stage_data *entry,
                         * unchanged in the other */
                        if (a_sha)
                                output("Removing %s", path);
-                       remove_file(1, 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;
@@ -1054,44 +1045,23 @@ static int process_entry(const char *path, struct stage_data *entry,
                        output("CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %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);
                        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;
 
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
                output("Auto-merging %s", path);
                o.path = a.path = b.path = (char *)path;
                hashcpy(o.sha1, o_sha);
@@ -1108,7 +1078,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("CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
 
                        if (index_only)
                                update_file(0, mfi.sha, mfi.mode, path);
@@ -1204,14 +1175,13 @@ 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,
+                int call_depth /* =0 */,
+                struct commit *ancestor /* =None */,
+                struct commit **result)
 {
        struct commit_list *ca = NULL, *iter;
        struct commit *merged_common_ancestors;
@@ -1235,13 +1205,10 @@ int merge(struct commit *h1,
        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");
        }
 
@@ -1311,6 +1278,7 @@ int main(int argc, char *argv[])
        const char *branch1, *branch2;
        struct commit *result, *h1, *h2;
 
+       git_config(git_default_config); /* core.filemode */
        original_index_file = getenv("GIT_INDEX_FILE");
 
        if (!original_index_file)