summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--merge-recursive.c177
-rwxr-xr-xt/t6026-merge-attr.sh71
2 files changed, 235 insertions, 13 deletions
diff --git a/merge-recursive.c b/merge-recursive.c
index 3b34401..8ec18ad 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);
diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh
index 5daa223..1732b60 100755
--- a/t/t6026-merge-attr.sh
+++ b/t/t6026-merge-attr.sh
@@ -30,8 +30,9 @@ test_expect_success setup '
echo Side >>$f && git add $f || break
done &&
test_tick &&
- git commit -m Side
+ git commit -m Side &&
+ git tag anchor
'
test_expect_success merge '
@@ -69,4 +70,72 @@ test_expect_success 'check merge result in working tree' '
'
+cat >./custom-merge <<\EOF
+#!/bin/sh
+
+orig="$1" ours="$2" theirs="$3" exit="$4"
+(
+ echo "orig is $orig"
+ echo "ours is $ours"
+ echo "theirs is $theirs"
+ echo "=== orig ==="
+ cat "$orig"
+ echo "=== ours ==="
+ cat "$ours"
+ echo "=== theirs ==="
+ cat "$theirs"
+) >"$ours+"
+cat "$ours+" >"$ours"
+rm -f "$ours+"
+exit "$exit"
+EOF
+chmod +x ./custom-merge
+
+test_expect_success 'custom merge backend' '
+
+ echo "* merge=union" >.gitattributes &&
+ echo "text merge=custom" >>.gitattributes &&
+
+ git reset --hard anchor &&
+ git config --replace-all \
+ merge.driver "custom ./custom-merge %O %A %B 0" &&
+
+ git merge master &&
+
+ cmp binary union &&
+ sed -e 1,3d text >check-1 &&
+ o=$(git-unpack-file master^:text) &&
+ a=$(git-unpack-file side^:text) &&
+ b=$(git-unpack-file master:text) &&
+ sh -c "./custom-merge $o $a $b 0" &&
+ sed -e 1,3d $a >check-2 &&
+ cmp check-1 check-2 &&
+ rm -f $o $a $b
+'
+
+test_expect_success 'custom merge backend' '
+
+ git reset --hard anchor &&
+ git config --replace-all \
+ merge.driver "custom ./custom-merge %O %A %B 1" &&
+
+ if git merge master
+ then
+ echo "Eh? should have conflicted"
+ false
+ else
+ echo "Ok, conflicted"
+ fi &&
+
+ cmp binary union &&
+ sed -e 1,3d text >check-1 &&
+ o=$(git-unpack-file master^:text) &&
+ a=$(git-unpack-file anchor:text) &&
+ b=$(git-unpack-file master:text) &&
+ sh -c "./custom-merge $o $a $b 0" &&
+ sed -e 1,3d $a >check-2 &&
+ cmp check-1 check-2 &&
+ rm -f $o $a $b
+'
+
test_done