git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 3/4] Allow specifying specialized merge-backend per path.

From
Junio C Hamano <junkio@cox.net>
Date
Apr 17, 2007, 08:08 UTC
Message-ID
<11767973191314-git-send-email-junkio@cox.net>
In-Reply-To
<11767973183627-git-send-email-junkio@cox.net>

This allows 'merge' attribute to control how the file-level three-way merge is done per path.

 - If you set 'merge' to true or leave unspecified, we use the
   built-in 3-way xdl-merge.
 - If you set 'merge' to false, the merge result is 'ours'.  But
   this still leaves the path conflicted, so that the mess can
   be sorted out by the user.  This is probably 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.

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_ours_merge() otherwise.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 merge-recursive.c |  133 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 126 insertions(+), 7 deletions(-)
diff --git a/merge-recursive.c b/merge-recursive.c
index 4eb62cf..a6c08a1 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,124 @@ 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));
+	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_ours_merge(mmfile_t *orig,
+			 mmfile_t *src1, const char *name1,
+			 mmfile_t *src2, const char *name2,
+			 mmbuffer_t *result)
+{
+	/*
+	 * Pretend the tentative merge result is "ours",
+	 * but still return "conflicted merge" status.
+	 */
+	result->ptr = src1->ptr;
+	result->size = src1->size;
+	src1->ptr = NULL;
+	return 1;
+}
+
+static struct {
+	const char *name;
+	ll_merge_fn fn;
+} ll_merge_fns[] = {
+	{ "3way", ll_xdl_merge },
+	{ "ours", ll_ours_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_ours_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 +786,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 +798,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);
-- 
1.5.1.1.821.g88bdb
Previous: Junio C HamanoNext: Junio C Hamano
Message 3 of 6 in “Allow more than true/false to attributes.”
  1. 1/4 Allow more than true/false to attributes.Junio C Hamano, Apr 17, 2007
  2. 2/4 merge-recursive: separate out xdl_merge() interface.Junio C Hamano, Apr 17, 2007
  3. 3/4 Allow specifying specialized merge-backend per path.Junio C Hamano, Apr 17, 2007
  4. Junio C HamanoApr 17, 2007
  5. Linus TorvaldsApr 17, 2007
  6. 4/4 Add a demonstration/test of customized merge.Junio C Hamano, Apr 17, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.