Skip to content

Commit a129d96

Browse files
author
Junio C Hamano
committed
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 <junkio@cox.net>
1 parent 3e5261a commit a129d96

File tree

1 file changed

+129
-7
lines changed

1 file changed

+129
-7
lines changed

merge-recursive.c

Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "unpack-trees.h"
1616
#include "path-list.h"
1717
#include "xdiff-interface.h"
18+
#include "attr.h"
1819

1920
static int subtree_merge;
2021

@@ -659,6 +660,127 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
659660
mm->size = size;
660661
}
661662

663+
/* Low-level merge functions */
664+
typedef int (*ll_merge_fn)(mmfile_t *orig,
665+
mmfile_t *src1, const char *name1,
666+
mmfile_t *src2, const char *name2,
667+
mmbuffer_t *result);
668+
669+
static int ll_xdl_merge(mmfile_t *orig,
670+
mmfile_t *src1, const char *name1,
671+
mmfile_t *src2, const char *name2,
672+
mmbuffer_t *result)
673+
{
674+
xpparam_t xpp;
675+
676+
memset(&xpp, 0, sizeof(xpp));
677+
return xdl_merge(orig,
678+
src1, name1,
679+
src2, name2,
680+
&xpp, XDL_MERGE_ZEALOUS,
681+
result);
682+
}
683+
684+
static int ll_union_merge(mmfile_t *orig,
685+
mmfile_t *src1, const char *name1,
686+
mmfile_t *src2, const char *name2,
687+
mmbuffer_t *result)
688+
{
689+
char *src, *dst;
690+
long size;
691+
const int marker_size = 7;
692+
693+
int status = ll_xdl_merge(orig, src1, NULL, src2, NULL, result);
694+
if (status <= 0)
695+
return status;
696+
size = result->size;
697+
src = dst = result->ptr;
698+
while (size) {
699+
char ch;
700+
if ((marker_size < size) &&
701+
(*src == '<' || *src == '=' || *src == '>')) {
702+
int i;
703+
ch = *src;
704+
for (i = 0; i < marker_size; i++)
705+
if (src[i] != ch)
706+
goto not_a_marker;
707+
if (src[marker_size] != '\n')
708+
goto not_a_marker;
709+
src += marker_size + 1;
710+
size -= marker_size + 1;
711+
continue;
712+
}
713+
not_a_marker:
714+
do {
715+
ch = *src++;
716+
*dst++ = ch;
717+
size--;
718+
} while (ch != '\n' && size);
719+
}
720+
result->size = dst - result->ptr;
721+
return 0;
722+
}
723+
724+
static int ll_binary_merge(mmfile_t *orig,
725+
mmfile_t *src1, const char *name1,
726+
mmfile_t *src2, const char *name2,
727+
mmbuffer_t *result)
728+
{
729+
/*
730+
* The tentative merge result is "ours" for the final round,
731+
* or common ancestor for an internal merge. Still return
732+
* "conflicted merge" status.
733+
*/
734+
mmfile_t *stolen = index_only ? orig : src1;
735+
736+
result->ptr = stolen->ptr;
737+
result->size = stolen->size;
738+
stolen->ptr = NULL;
739+
return 1;
740+
}
741+
742+
static struct {
743+
const char *name;
744+
ll_merge_fn fn;
745+
} ll_merge_fns[] = {
746+
{ "text", ll_xdl_merge },
747+
{ "binary", ll_binary_merge },
748+
{ "union", ll_union_merge },
749+
{ NULL, NULL },
750+
};
751+
752+
static ll_merge_fn find_ll_merge_fn(void *merge_attr)
753+
{
754+
const char *name;
755+
int i;
756+
757+
if (ATTR_TRUE(merge_attr) || ATTR_UNSET(merge_attr))
758+
return ll_xdl_merge;
759+
else if (ATTR_FALSE(merge_attr))
760+
return ll_binary_merge;
761+
762+
/* Otherwise merge_attr may name the merge function */
763+
name = merge_attr;
764+
for (i = 0; ll_merge_fns[i].name; i++)
765+
if (!strcmp(ll_merge_fns[i].name, name))
766+
return ll_merge_fns[i].fn;
767+
768+
/* default to the 3-way */
769+
return ll_xdl_merge;
770+
}
771+
772+
static void *git_path_check_merge(const char *path)
773+
{
774+
static struct git_attr_check attr_merge_check;
775+
776+
if (!attr_merge_check.attr)
777+
attr_merge_check.attr = git_attr("merge", 5);
778+
779+
if (git_checkattr(path, 1, &attr_merge_check))
780+
return ATTR__UNSET;
781+
return attr_merge_check.value;
782+
}
783+
662784
static int ll_merge(mmbuffer_t *result_buf,
663785
struct diff_filespec *o,
664786
struct diff_filespec *a,
@@ -667,9 +789,10 @@ static int ll_merge(mmbuffer_t *result_buf,
667789
const char *branch2)
668790
{
669791
mmfile_t orig, src1, src2;
670-
xpparam_t xpp;
671792
char *name1, *name2;
672793
int merge_status;
794+
void *merge_attr;
795+
ll_merge_fn fn;
673796

674797
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
675798
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -678,12 +801,11 @@ static int ll_merge(mmbuffer_t *result_buf,
678801
fill_mm(a->sha1, &src1);
679802
fill_mm(b->sha1, &src2);
680803

681-
memset(&xpp, 0, sizeof(xpp));
682-
merge_status = xdl_merge(&orig,
683-
&src1, name1,
684-
&src2, name2,
685-
&xpp, XDL_MERGE_ZEALOUS,
686-
result_buf);
804+
merge_attr = git_path_check_merge(a->path);
805+
fn = find_ll_merge_fn(merge_attr);
806+
807+
merge_status = fn(&orig, &src1, name1, &src2, name2, result_buf);
808+
687809
free(name1);
688810
free(name2);
689811
free(orig.ptr);

0 commit comments

Comments
 (0)