From 3e5261a24071ca23c3514c0ebd5ee55f1e79d9cc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 16 Apr 2007 21:58:01 -0700 Subject: merge-recursive: separate out xdl_merge() interface. This just moves code around to make the actual call to xdl_merge() into a separate function. Signed-off-by: Junio C Hamano --- merge-recursive.c | 56 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 21 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 3096594b3..4eb62cf64 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -659,6 +659,39 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm) mm->size = size; } +static int ll_merge(mmbuffer_t *result_buf, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) +{ + mmfile_t orig, src1, src2; + 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); + return merge_status; +} + static struct merge_file_info merge_file(struct diff_filespec *o, struct diff_filespec *a, struct diff_filespec *b, const char *branch1, const char *branch2) @@ -687,30 +720,11 @@ 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)) { - 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); + merge_status = ll_merge(&result_buf, o, a, b, + branch1, branch2); if ((merge_status < 0) || !result_buf.ptr) die("Failed to execute internal merge"); -- cgit v1.2.1 From a129d96f4144215711e379565af97f6a82197f4f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 16 Apr 2007 22:59:18 -0700 Subject: Allow specifying specialized merge-backend per path. 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 --- merge-recursive.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 7 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 4eb62cf64..3b34401d0 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -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); -- cgit v1.2.1 From f3ef6b6bbe9bfd3d09130f7e26b87dbe11b93c5b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 17 Apr 2007 22:51:45 -0700 Subject: Custom low-level merge driver support. This allows users to specify custom low-level merge driver per path, using the attributes mechanism. Just like you can specify one of built-in "text", "binary", "union" low-level merge drivers by saying: * merge=text .gitignore merge=union *.jpg merge=binary pick a name of your favorite merge driver, and assign it as the value of the 'merge' attribute. A custom low-level merge driver is defined via the config mechanism. This patch introduces 'merge.driver', a multi-valued configuration. Its value is the name (i.e. the one you use as the value of 'merge' attribute) followed by a command line specification. The command line can contain %O, %A, and %B to be interpolated with the names of temporary files that hold the common ancestor version, the version from your branch, and the version from the other branch, and the resulting command is spawned. The low-level merge driver is expected to update the temporary file for your branch (i.e. %A) with the result and exit with status 0 for a clean merge, and non-zero status for a conflicted merge. A new test in t6026 demonstrates a sample usage. Signed-off-by: Junio C Hamano --- merge-recursive.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 12 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 3b34401d0..8ec18ad57 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -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); -- cgit v1.2.1 From be89cb239e8ec02e23015675cc8b2d60992a6cfc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 01:47:21 -0700 Subject: Allow the default low-level merge driver to be configured. When no 'merge' attribute is given to a path, merge-recursive uses the built-in xdl-merge as the low-level merge driver. A new configuration item 'merge.default' can name a low-level merge driver of user's choice to be used instead. Signed-off-by: Junio C Hamano --- merge-recursive.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 8ec18ad57..598300097 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -839,12 +839,18 @@ static struct user_merge_fn { char *cmdline; char b_[1]; } *ll_user_merge_fns, **ll_user_merge_fns_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; + if (!strcmp(var, "merge.default")) { + default_ll_merge = strdup(value); + return 0; + } + if (strcmp(var, "merge.driver")) return 0; if (!value) @@ -900,8 +906,12 @@ static ll_merge_fn find_ll_merge_fn(void *merge_attr, const char **cmdline) 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 if (ATTR_UNSET(merge_attr)) { + if (!default_ll_merge) + return ll_xdl_merge; + else + name = default_ll_merge; + } else name = merge_attr; -- cgit v1.2.1 From 153920da5b62024c0aceef23252b82ad18e5fe22 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 11:27:32 -0700 Subject: Custom low-level merge driver: change the configuration scheme. This changes the configuration syntax for defining a low-level merge driver to be: [merge "<>"] driver = "<>" name = "<>" 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.<.name variable. The demonstration in t6026 has also been updated. Signed-off-by: Junio C Hamano --- merge-recursive.c | 202 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 125 insertions(+), 77 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 598300097..0f5c28eaf 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -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..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..var2 + * after seeing merge..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..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); -- cgit v1.2.1 From 3086486d326b00ce308208e62e0e0de831f3563b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 12:18:25 -0700 Subject: Allow low-level driver to specify different behaviour during internal merge. This allows [merge "drivername"] to have a variable "recursive" that names a different low-level merge driver to be used when merging common ancestors to come up with a virtual ancestor. Signed-off-by: Junio C Hamano --- merge-recursive.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 0f5c28eaf..7b5ca8e71 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -677,6 +677,7 @@ struct ll_merge_driver { const char *name; const char *description; ll_merge_fn fn; + const char *recursive; struct ll_merge_driver *next; char *cmdline; }; @@ -934,6 +935,13 @@ static int read_merge_config(const char *var, const char *value) return 0; } + if (!strcmp("recursive", ep)) { + if (!value) + return error("%s: lacks value", var); + fn->recursive = strdup(value); + return 0; + } + return 0; } @@ -1013,6 +1021,10 @@ static int ll_merge(mmbuffer_t *result_buf, merge_attr = git_path_check_merge(a->path); driver = find_ll_merge_driver(merge_attr); + if (index_only && driver->recursive) { + merge_attr = git_attr(driver->recursive, strlen(driver->recursive)); + driver = find_ll_merge_driver(merge_attr); + } merge_status = driver->fn(driver, a->path, &orig, &src1, name1, &src2, name2, result_buf); -- cgit v1.2.1 From a5e92abde61d59a8612c5b87d0bae681e90f7fdb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 16:16:37 -0700 Subject: Fix funny types used in attribute value representation It was bothering me a lot that I abused small integer values casted to (void *) to represent non string values in gitattributes. This corrects it by making the type of attribute values (const char *), and using the address of a few statically allocated character buffer to denote true/false. Unset attributes are represented as having NULLs as their values. Added in-header documentation to explain how git_checkattr() routine should be called. Signed-off-by: Junio C Hamano --- merge-recursive.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 7b5ca8e71..ec8438b46 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -953,7 +953,7 @@ static void initialize_ll_merge(void) git_config(read_merge_config); } -static const struct ll_merge_driver *find_ll_merge_driver(void *merge_attr) +static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) { struct ll_merge_driver *fn; const char *name; @@ -986,7 +986,7 @@ static const struct ll_merge_driver *find_ll_merge_driver(void *merge_attr) return &ll_merge_drv[LL_TEXT_MERGE]; } -static void *git_path_check_merge(const char *path) +static const char *git_path_check_merge(const char *path) { static struct git_attr_check attr_merge_check; @@ -994,7 +994,7 @@ static void *git_path_check_merge(const char *path) attr_merge_check.attr = git_attr("merge", 5); if (git_checkattr(path, 1, &attr_merge_check)) - return ATTR__UNSET; + return NULL; return attr_merge_check.value; } @@ -1008,7 +1008,7 @@ static int ll_merge(mmbuffer_t *result_buf, mmfile_t orig, src1, src2; char *name1, *name2; int merge_status; - void *merge_attr; + const char *ll_driver_name; const struct ll_merge_driver *driver; name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); @@ -1018,11 +1018,14 @@ static int ll_merge(mmbuffer_t *result_buf, fill_mm(a->sha1, &src1); fill_mm(b->sha1, &src2); - merge_attr = git_path_check_merge(a->path); - driver = find_ll_merge_driver(merge_attr); + ll_driver_name = git_path_check_merge(a->path); + driver = find_ll_merge_driver(ll_driver_name); if (index_only && driver->recursive) { - merge_attr = git_attr(driver->recursive, strlen(driver->recursive)); + void *merge_attr; + + ll_driver_name = driver->recursive; + merge_attr = git_attr(ll_driver_name, strlen(ll_driver_name)); driver = find_ll_merge_driver(merge_attr); } merge_status = driver->fn(driver, a->path, -- cgit v1.2.1 From 15ba3af2d5056313fa19ceb0cb7f7cb3cdd54f16 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 19:05:57 -0700 Subject: Counto-fix in merge-recursive When the configuration has variables unrelated to low-level merge drivers (e.g. merge.summary), the code failed to ignore them but did something totally senseless. Signed-off-by: Junio C Hamano --- merge-recursive.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index ec8438b46..65c018b3e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -809,6 +809,9 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, int status, fd, i; struct stat st; + if (fn->cmdline == NULL) + die("custom merge driver %s lacks command line.", fn->name); + result->ptr = NULL; result->size = 0; create_temp(orig, temp[0]); @@ -879,7 +882,7 @@ static int read_merge_config(const char *var, const char *value) * 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) + if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5) return 0; /* -- cgit v1.2.1 From d56dbd67097a84dac1dbdf28c1a254f63f93724a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 Apr 2007 19:22:57 -0700 Subject: Simplify code to find recursive merge driver. There is no need to intern the string to git_attr, as we are already dealing with the name of the driver there. Signed-off-by: Junio C Hamano --- merge-recursive.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 65c018b3e..96e461c73 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1024,13 +1024,8 @@ static int ll_merge(mmbuffer_t *result_buf, ll_driver_name = git_path_check_merge(a->path); driver = find_ll_merge_driver(ll_driver_name); - if (index_only && driver->recursive) { - void *merge_attr; - - ll_driver_name = driver->recursive; - merge_attr = git_attr(ll_driver_name, strlen(ll_driver_name)); - driver = find_ll_merge_driver(merge_attr); - } + if (index_only && driver->recursive) + driver = find_ll_merge_driver(driver->recursive); merge_status = driver->fn(driver, a->path, &orig, &src1, name1, &src2, name2, result_buf); -- cgit v1.2.1 From e87b1c943a50af9ab51df20b3419cbffa4e75484 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 21 Apr 2007 00:05:31 -0700 Subject: Fix bogus linked-list management for user defined merge drivers. ll_user_merge_tail is supposed to point at the pointer to be updated to point at a newly created item. Signed-off-by: Junio C Hamano --- merge-recursive.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'merge-recursive.c') diff --git a/merge-recursive.c b/merge-recursive.c index 96e461c73..3d395895f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -902,8 +902,9 @@ static int read_merge_config(const char *var, const char *value) namebuf[namelen] = 0; fn->name = namebuf; fn->fn = ll_ext_merge; - fn->next = *ll_user_merge_tail; + fn->next = NULL; *ll_user_merge_tail = fn; + ll_user_merge_tail = &(fn->next); } ep++; -- cgit v1.2.1