Custom low-level merge driver: change the configuration scheme.
authorJunio C Hamano <junkio@cox.net>
Wed, 18 Apr 2007 18:27:32 +0000 (11:27 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 18 Apr 2007 19:30:26 +0000 (12:30 -0700)
This changes the configuration syntax for defining a low-level
merge driver to be:

[merge "<<drivername>>"]
driver = "<<command line>>"
name = "<<driver description>>"

which is much nicer to read and is extensible.  Credit goes to
Martin Waitz and Linus.

In addition, when we use an external low-level merge driver, it
is reported as an extra output from merge-recursive, using the
value of merge.<<drivername>.name variable.

The demonstration in t6026 has also been updated.

Signed-off-by: Junio C Hamano <junkio@cox.net>
merge-recursive.c
t/t6026-merge-attr.sh

index 5983000971dbe5f71156f941beceb1a9911bf864..0f5c28eaff1ccbfd2abd288e9c72d43d86f1d20d 100644 (file)
@@ -661,14 +661,31 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
        mm->size = size;
 }
 
-/* Low-level merge functions */
-typedef int (*ll_merge_fn)(const char *cmd,
+/*
+ * Customizable low-level merge drivers support.
+ */
+
+struct ll_merge_driver;
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+                          const char *path,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
                           mmbuffer_t *result);
 
-static int ll_xdl_merge(const char *cmd__unused,
+struct ll_merge_driver {
+       const char *name;
+       const char *description;
+       ll_merge_fn fn;
+       struct ll_merge_driver *next;
+       char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+                       const char *path_unused,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
@@ -684,7 +701,8 @@ static int ll_xdl_merge(const char *cmd__unused,
                         result);
 }
 
-static int ll_union_merge(const char *cmd__unused,
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+                         const char *path_unused,
                          mmfile_t *orig,
                          mmfile_t *src1, const char *name1,
                          mmfile_t *src2, const char *name2,
@@ -694,8 +712,8 @@ static int ll_union_merge(const char *cmd__unused,
        long size;
        const int marker_size = 7;
 
-       int status = ll_xdl_merge(cmd__unused, orig,
-                                 src1, NULL, src2, NULL, result);
+       int status = ll_xdl_merge(drv_unused, path_unused,
+                                 orig, src1, NULL, src2, NULL, result);
        if (status <= 0)
                return status;
        size = result->size;
@@ -726,7 +744,8 @@ static int ll_union_merge(const char *cmd__unused,
        return 0;
 }
 
-static int ll_binary_merge(const char *cmd__unused,
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          const char *path_unused,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
@@ -745,14 +764,13 @@ static int ll_binary_merge(const char *cmd__unused,
        return 1;
 }
 
-static struct {
-       const char *name;
-       ll_merge_fn fn;
-} ll_merge_fns[] = {
-       { "binary", ll_binary_merge },
-       { "text", ll_xdl_merge },
-       { "union", ll_union_merge },
-       { NULL, NULL },
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+       { "binary", "built-in binary merge", ll_binary_merge },
+       { "text", "built-in 3-way text merge", ll_xdl_merge },
+       { "union", "built-in union merge", ll_union_merge },
 };
 
 static void create_temp(mmfile_t *src, char *path)
@@ -768,7 +786,11 @@ static void create_temp(mmfile_t *src, char *path)
        close(fd);
 }
 
-static int ll_ext_merge(const char *cmd,
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+                       const char *path,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
@@ -796,7 +818,10 @@ static int ll_ext_merge(const char *cmd,
        interp_set_entry(table, 1, temp[1]);
        interp_set_entry(table, 2, temp[2]);
 
-       interpolate(cmdbuf, sizeof(cmdbuf), cmd, table, 3);
+       output(1, "merging %s using %s", path,
+              fn->description ? fn->description : fn->name);
+
+       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
 
        memset(&child, 0, sizeof(child));
        child.argv = args;
@@ -833,101 +858,124 @@ static int ll_ext_merge(const char *cmd,
 /*
  * 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 struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
 static const char *default_ll_merge;
 
 static int read_merge_config(const char *var, const char *value)
 {
-       struct user_merge_fn *fn;
-       int blen, nlen;
+       struct ll_merge_driver *fn;
+       const char *ep, *name;
+       int namelen;
 
        if (!strcmp(var, "merge.default")) {
-               default_ll_merge = strdup(value);
+               if (value)
+                       default_ll_merge = strdup(value);
                return 0;
        }
 
-       if (strcmp(var, "merge.driver"))
+       /*
+        * We are not interested in anything but "merge.<name>.variable";
+        * especially, we do not want to look at variables such as
+        * "merge.summary", "merge.tool", and "merge.verbosity".
+        */
+       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 6)
                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.
+        * Find existing one as we might be processing merge.<name>.var2
+        * after seeing merge.<name>.var1.
         */
-       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;
+       name = var + 6;
+       namelen = ep - name;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+                       break;
+       if (!fn) {
+               char *namebuf;
+               fn = xcalloc(1, sizeof(struct ll_merge_driver));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               fn->name = namebuf;
+               fn->fn = ll_ext_merge;
+               fn->next = *ll_user_merge_tail;
+               *ll_user_merge_tail = fn;
+       }
+
+       ep++;
+
+       if (!strcmp("name", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->description = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("driver", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               /*
+                * merge.<name>.driver specifies the command line:
+                *
+                *      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 zero exit
+                * status.
+                */
+               fn->cmdline = strdup(value);
+               return 0;
+       }
+
        return 0;
 }
 
 static void initialize_ll_merge(void)
 {
-       if (ll_user_merge_fns_tail)
+       if (ll_user_merge_tail)
                return;
-       ll_user_merge_fns_tail = &ll_user_merge_fns;
+       ll_user_merge_tail = &ll_user_merge;
        git_config(read_merge_config);
 }
 
-static ll_merge_fn find_ll_merge_fn(void *merge_attr, const char **cmdline)
+static const struct ll_merge_driver *find_ll_merge_driver(void *merge_attr)
 {
-       struct user_merge_fn *fn;
+       struct ll_merge_driver *fn;
        const char *name;
        int i;
 
        initialize_ll_merge();
 
        if (ATTR_TRUE(merge_attr))
-               return ll_xdl_merge;
+               return &ll_merge_drv[LL_TEXT_MERGE];
        else if (ATTR_FALSE(merge_attr))
-               return ll_binary_merge;
+               return &ll_merge_drv[LL_BINARY_MERGE];
        else if (ATTR_UNSET(merge_attr)) {
                if (!default_ll_merge)
-                       return ll_xdl_merge;
+                       return &ll_merge_drv[LL_TEXT_MERGE];
                else
                        name = default_ll_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;
-               }
-       }
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strcmp(fn->name, name))
+                       return fn;
 
-       for (i = 0; ll_merge_fns[i].name; i++)
-               if (!strcmp(ll_merge_fns[i].name, name))
-                       return ll_merge_fns[i].fn;
+       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+               if (!strcmp(ll_merge_drv[i].name, name))
+                       return &ll_merge_drv[i];
 
        /* default to the 3-way */
-       return ll_xdl_merge;
+       return &ll_merge_drv[LL_TEXT_MERGE];
 }
 
 static void *git_path_check_merge(const char *path)
@@ -953,8 +1001,7 @@ static int ll_merge(mmbuffer_t *result_buf,
        char *name1, *name2;
        int merge_status;
        void *merge_attr;
-       ll_merge_fn fn;
-       const char *driver = NULL;
+       const struct ll_merge_driver *driver;
 
        name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
        name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -964,10 +1011,11 @@ 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, &driver);
+       driver = find_ll_merge_driver(merge_attr);
 
-       merge_status = fn(driver, &orig,
-                         &src1, name1, &src2, name2, result_buf);
+       merge_status = driver->fn(driver, a->path,
+                                 &orig, &src1, name1, &src2, name2,
+                                 result_buf);
 
        free(name1);
        free(name2);
index 1732b60ed8be3c8bd8ea39afc62205db4fccc228..56fc34176859b81137b4d88af90398b9a74a18f7 100755 (executable)
@@ -98,7 +98,9 @@ test_expect_success 'custom merge backend' '
 
        git reset --hard anchor &&
        git config --replace-all \
-       merge.driver "custom ./custom-merge %O %A %B 0" &&
+       merge.custom.driver "./custom-merge %O %A %B 0" &&
+       git config --replace-all \
+       merge.custom.name "custom merge driver for testing" &&
 
        git merge master &&
 
@@ -117,7 +119,9 @@ test_expect_success 'custom merge backend' '
 
        git reset --hard anchor &&
        git config --replace-all \
-       merge.driver "custom ./custom-merge %O %A %B 1" &&
+       merge.custom.driver "./custom-merge %O %A %B 1" &&
+       git config --replace-all \
+       merge.custom.name "custom merge driver for testing" &&
 
        if git merge master
        then