Code

Custom low-level merge driver support.
[git.git] / merge-recursive.c
index 3b34401d0b25126b7955c0d44dad3e69a9592129..8ec18ad5779c5c1de254411167533eb6ac55089f 100644 (file)
@@ -15,6 +15,7 @@
 #include "unpack-trees.h"
 #include "path-list.h"
 #include "xdiff-interface.h"
+#include "interpolate.h"
 #include "attr.h"
 
 static int subtree_merge;
@@ -661,12 +662,14 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 }
 
 /* Low-level merge functions */
-typedef int (*ll_merge_fn)(mmfile_t *orig,
+typedef int (*ll_merge_fn)(const char *cmd,
+                          mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
                           mmbuffer_t *result);
 
-static int ll_xdl_merge(mmfile_t *orig,
+static int ll_xdl_merge(const char *cmd__unused,
+                       mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
                        mmbuffer_t *result)
@@ -681,7 +684,8 @@ static int ll_xdl_merge(mmfile_t *orig,
                         result);
 }
 
-static int ll_union_merge(mmfile_t *orig,
+static int ll_union_merge(const char *cmd__unused,
+                         mmfile_t *orig,
                          mmfile_t *src1, const char *name1,
                          mmfile_t *src2, const char *name2,
                          mmbuffer_t *result)
@@ -690,7 +694,8 @@ static int ll_union_merge(mmfile_t *orig,
        long size;
        const int marker_size = 7;
 
-       int status = ll_xdl_merge(orig, src1, NULL, src2, NULL, result);
+       int status = ll_xdl_merge(cmd__unused, orig,
+                                 src1, NULL, src2, NULL, result);
        if (status <= 0)
                return status;
        size = result->size;
@@ -721,7 +726,8 @@ static int ll_union_merge(mmfile_t *orig,
        return 0;
 }
 
-static int ll_binary_merge(mmfile_t *orig,
+static int ll_binary_merge(const char *cmd__unused,
+                          mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
                           mmbuffer_t *result)
@@ -743,24 +749,169 @@ static struct {
        const char *name;
        ll_merge_fn fn;
 } ll_merge_fns[] = {
-       { "text", ll_xdl_merge },
        { "binary", ll_binary_merge },
+       { "text", ll_xdl_merge },
        { "union", ll_union_merge },
        { NULL, NULL },
 };
 
-static ll_merge_fn find_ll_merge_fn(void *merge_attr)
+static void create_temp(mmfile_t *src, char *path)
 {
+       int fd;
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write_in_full(fd, src->ptr, src->size) != src->size)
+               die("unable to write temp-file");
+       close(fd);
+}
+
+static int ll_ext_merge(const char *cmd,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       char temp[3][50];
+       char cmdbuf[2048];
+       struct interp table[] = {
+               { "%O" },
+               { "%A" },
+               { "%B" },
+       };
+       struct child_process child;
+       const char *args[20];
+       int status, fd, i;
+       struct stat st;
+
+       result->ptr = NULL;
+       result->size = 0;
+       create_temp(orig, temp[0]);
+       create_temp(src1, temp[1]);
+       create_temp(src2, temp[2]);
+
+       interp_set_entry(table, 0, temp[0]);
+       interp_set_entry(table, 1, temp[1]);
+       interp_set_entry(table, 2, temp[2]);
+
+       interpolate(cmdbuf, sizeof(cmdbuf), cmd, table, 3);
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = "sh";
+       args[1] = "-c";
+       args[2] = cmdbuf;
+       args[3] = NULL;
+
+       status = run_command(&child);
+       if (status < -ERR_RUN_COMMAND_FORK)
+               ; /* failure in run-command */
+       else
+               status = -status;
+       fd = open(temp[1], O_RDONLY);
+       if (fd < 0)
+               goto bad;
+       if (fstat(fd, &st))
+               goto close_bad;
+       result->size = st.st_size;
+       result->ptr = xmalloc(result->size + 1);
+       if (read_in_full(fd, result->ptr, result->size) != result->size) {
+               free(result->ptr);
+               result->ptr = NULL;
+               result->size = 0;
+       }
+ close_bad:
+       close(fd);
+ bad:
+       for (i = 0; i < 3; i++)
+               unlink(temp[i]);
+       return status;
+}
+
+/*
+ * merge.default and merge.driver configuration items
+ */
+static struct user_merge_fn {
+       struct user_merge_fn *next;
+       const char *name;
+       char *cmdline;
+       char b_[1];
+} *ll_user_merge_fns, **ll_user_merge_fns_tail;
+
+static int read_merge_config(const char *var, const char *value)
+{
+       struct user_merge_fn *fn;
+       int blen, nlen;
+
+       if (strcmp(var, "merge.driver"))
+               return 0;
+       if (!value)
+               return error("%s: lacks value", var);
+       /*
+        * merge.driver is a multi-valued configuration, whose value is
+        * of form:
+        *
+        *      name command-line
+        *
+        * The command-line will be interpolated with the following
+        * tokens and is given to the shell:
+        *
+        *    %O - temporary file name for the merge base.
+        *    %A - temporary file name for our version.
+        *    %B - temporary file name for the other branches' version.
+        *
+        * The external merge driver should write the results in the file
+        * named by %A, and signal that it has done with exit status 0.
+        */
+       for (nlen = -1, blen = 0; value[blen]; blen++)
+               if (nlen < 0 && isspace(value[blen]))
+                       nlen = blen;
+       if (nlen < 0)
+               return error("%s '%s': lacks command line", var, value);
+       fn = xcalloc(1, sizeof(struct user_merge_fn) + blen + 1);
+       memcpy(fn->b_, value, blen + 1);
+       fn->name = fn->b_;
+       fn->b_[nlen] = 0;
+       fn->cmdline = fn->b_ + nlen + 1;
+       fn->next = *ll_user_merge_fns_tail;
+       *ll_user_merge_fns_tail = fn;
+       return 0;
+}
+
+static void initialize_ll_merge(void)
+{
+       if (ll_user_merge_fns_tail)
+               return;
+       ll_user_merge_fns_tail = &ll_user_merge_fns;
+       git_config(read_merge_config);
+}
+
+static ll_merge_fn find_ll_merge_fn(void *merge_attr, const char **cmdline)
+{
+       struct user_merge_fn *fn;
        const char *name;
        int i;
 
-       if (ATTR_TRUE(merge_attr) || ATTR_UNSET(merge_attr))
+       initialize_ll_merge();
+
+       if (ATTR_TRUE(merge_attr))
                return ll_xdl_merge;
        else if (ATTR_FALSE(merge_attr))
                return ll_binary_merge;
+       else if (ATTR_UNSET(merge_attr))
+               return ll_xdl_merge;
+       else
+               name = merge_attr;
+
+       for (fn = ll_user_merge_fns; fn; fn = fn->next) {
+               if (!strcmp(fn->name, name)) {
+                       *cmdline = fn->cmdline;
+                       return ll_ext_merge;
+               }
+       }
 
-       /* Otherwise merge_attr may name the merge function */
-       name = merge_attr;
        for (i = 0; ll_merge_fns[i].name; i++)
                if (!strcmp(ll_merge_fns[i].name, name))
                        return ll_merge_fns[i].fn;
@@ -793,6 +944,7 @@ static int ll_merge(mmbuffer_t *result_buf,
        int merge_status;
        void *merge_attr;
        ll_merge_fn fn;
+       const char *driver = NULL;
 
        name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
        name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -802,9 +954,10 @@ static int ll_merge(mmbuffer_t *result_buf,
        fill_mm(b->sha1, &src2);
 
        merge_attr = git_path_check_merge(a->path);
-       fn = find_ll_merge_fn(merge_attr);
+       fn = find_ll_merge_fn(merge_attr, &driver);
 
-       merge_status = fn(&orig, &src1, name1, &src2, name2, result_buf);
+       merge_status = fn(driver, &orig,
+                         &src1, name1, &src2, name2, result_buf);
 
        free(name1);
        free(name2);