Allow specifying specialized merge-backend per path.
authorJunio C Hamano <junkio@cox.net>
Tue, 17 Apr 2007 05:59:18 +0000 (22:59 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 17 Apr 2007 21:11:20 +0000 (14:11 -0700)
This allows 'merge' attribute to control how the file-level
three-way merge is done per path.

 - If you set 'merge' to true, leave it unspecified, or set it
   to "text", we use the built-in 3-way xdl-merge.

 - If you set 'merge' to false, or set it to "binary, the
   "binary" merge is done.  The merge result is the blob from
   'our' tree, but this still leaves the path conflicted, so
   that the mess can be sorted out by the user.  This is
   obviously meant to be useful for binary files.

 - 'merge=union' (this is the first example of a string valued
   attribute, introduced in the previous one) uses the "union"
   merge.  The "union" merge takes lines in conflicted hunks
   from both sides, which is useful for line-oriented files such
   as .gitignore.

Instead fo setting merge to 'true' or 'false' by using 'merge'
or '-merge', setting it explicitly to "text" or "binary" will
become useful once we start allowing custom per-path backends to
be added, and allow them to be activated for the default
(i.e. 'merge' attribute specified to 'true' or 'false') case,
using some other mechanisms.  Setting merge attribute to "text"
or "binary" will be a way to explicitly request to override such
a custom default for selected paths.

Currently there is no way to specify random programs but it
should be trivial for motivated contributors to add later.

There is one caveat, though.  ll_merge() is called for both
internal ancestor merge and the outer "final" merge.  I think an
interactive custom per-path merge backend should refrain from
going interactive when performing an internal merge (you can
tell it by checking call_depth) and instead just call either
ll_xdl_merge() if the content is text, or call ll_binary_merge()
otherwise.

Signed-off-by: Junio C Hamano <junkio@cox.net>
merge-recursive.c

index 4eb62cf64ac13bca44f2f04349e71fb16088c719..3b34401d0b25126b7955c0d44dad3e69a9592129 100644 (file)
@@ -15,6 +15,7 @@
 #include "unpack-trees.h"
 #include "path-list.h"
 #include "xdiff-interface.h"
+#include "attr.h"
 
 static int subtree_merge;
 
@@ -659,6 +660,127 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
        mm->size = size;
 }
 
+/* Low-level merge functions */
+typedef int (*ll_merge_fn)(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,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       xpparam_t xpp;
+
+       memset(&xpp, 0, sizeof(xpp));
+       return xdl_merge(orig,
+                        src1, name1,
+                        src2, name2,
+                        &xpp, XDL_MERGE_ZEALOUS,
+                        result);
+}
+
+static int ll_union_merge(mmfile_t *orig,
+                         mmfile_t *src1, const char *name1,
+                         mmfile_t *src2, const char *name2,
+                         mmbuffer_t *result)
+{
+       char *src, *dst;
+       long size;
+       const int marker_size = 7;
+
+       int status = ll_xdl_merge(orig, src1, NULL, src2, NULL, result);
+       if (status <= 0)
+               return status;
+       size = result->size;
+       src = dst = result->ptr;
+       while (size) {
+               char ch;
+               if ((marker_size < size) &&
+                   (*src == '<' || *src == '=' || *src == '>')) {
+                       int i;
+                       ch = *src;
+                       for (i = 0; i < marker_size; i++)
+                               if (src[i] != ch)
+                                       goto not_a_marker;
+                       if (src[marker_size] != '\n')
+                               goto not_a_marker;
+                       src += marker_size + 1;
+                       size -= marker_size + 1;
+                       continue;
+               }
+       not_a_marker:
+               do {
+                       ch = *src++;
+                       *dst++ = ch;
+                       size--;
+               } while (ch != '\n' && size);
+       }
+       result->size = dst - result->ptr;
+       return 0;
+}
+
+static int ll_binary_merge(mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result)
+{
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = index_only ? orig : src1;
+
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+}
+
+static struct {
+       const char *name;
+       ll_merge_fn fn;
+} ll_merge_fns[] = {
+       { "text", ll_xdl_merge },
+       { "binary", ll_binary_merge },
+       { "union", ll_union_merge },
+       { NULL, NULL },
+};
+
+static ll_merge_fn find_ll_merge_fn(void *merge_attr)
+{
+       const char *name;
+       int i;
+
+       if (ATTR_TRUE(merge_attr) || ATTR_UNSET(merge_attr))
+               return ll_xdl_merge;
+       else if (ATTR_FALSE(merge_attr))
+               return ll_binary_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;
+
+       /* default to the 3-way */
+       return ll_xdl_merge;
+}
+
+static void *git_path_check_merge(const char *path)
+{
+       static struct git_attr_check attr_merge_check;
+
+       if (!attr_merge_check.attr)
+               attr_merge_check.attr = git_attr("merge", 5);
+
+       if (git_checkattr(path, 1, &attr_merge_check))
+               return ATTR__UNSET;
+       return attr_merge_check.value;
+}
+
 static int ll_merge(mmbuffer_t *result_buf,
                    struct diff_filespec *o,
                    struct diff_filespec *a,
@@ -667,9 +789,10 @@ static int ll_merge(mmbuffer_t *result_buf,
                    const char *branch2)
 {
        mmfile_t orig, src1, src2;
-       xpparam_t xpp;
        char *name1, *name2;
        int merge_status;
+       void *merge_attr;
+       ll_merge_fn fn;
 
        name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
        name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -678,12 +801,11 @@ static int ll_merge(mmbuffer_t *result_buf,
        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);
+       merge_attr = git_path_check_merge(a->path);
+       fn = find_ll_merge_fn(merge_attr);
+
+       merge_status = fn(&orig, &src1, name1, &src2, name2, result_buf);
+
        free(name1);
        free(name2);
        free(orig.ptr);