Skip to content

Commit 153920d

Browse files
author
Junio C Hamano
committed
Custom low-level merge driver: change the configuration scheme.
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>
1 parent be89cb2 commit 153920d

File tree

2 files changed

+131
-79
lines changed

2 files changed

+131
-79
lines changed

merge-recursive.c

Lines changed: 125 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -661,14 +661,31 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
661661
mm->size = size;
662662
}
663663

664-
/* Low-level merge functions */
665-
typedef int (*ll_merge_fn)(const char *cmd,
664+
/*
665+
* Customizable low-level merge drivers support.
666+
*/
667+
668+
struct ll_merge_driver;
669+
typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
670+
const char *path,
666671
mmfile_t *orig,
667672
mmfile_t *src1, const char *name1,
668673
mmfile_t *src2, const char *name2,
669674
mmbuffer_t *result);
670675

671-
static int ll_xdl_merge(const char *cmd__unused,
676+
struct ll_merge_driver {
677+
const char *name;
678+
const char *description;
679+
ll_merge_fn fn;
680+
struct ll_merge_driver *next;
681+
char *cmdline;
682+
};
683+
684+
/*
685+
* Built-in low-levels
686+
*/
687+
static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
688+
const char *path_unused,
672689
mmfile_t *orig,
673690
mmfile_t *src1, const char *name1,
674691
mmfile_t *src2, const char *name2,
@@ -684,7 +701,8 @@ static int ll_xdl_merge(const char *cmd__unused,
684701
result);
685702
}
686703

687-
static int ll_union_merge(const char *cmd__unused,
704+
static int ll_union_merge(const struct ll_merge_driver *drv_unused,
705+
const char *path_unused,
688706
mmfile_t *orig,
689707
mmfile_t *src1, const char *name1,
690708
mmfile_t *src2, const char *name2,
@@ -694,8 +712,8 @@ static int ll_union_merge(const char *cmd__unused,
694712
long size;
695713
const int marker_size = 7;
696714

697-
int status = ll_xdl_merge(cmd__unused, orig,
698-
src1, NULL, src2, NULL, result);
715+
int status = ll_xdl_merge(drv_unused, path_unused,
716+
orig, src1, NULL, src2, NULL, result);
699717
if (status <= 0)
700718
return status;
701719
size = result->size;
@@ -726,7 +744,8 @@ static int ll_union_merge(const char *cmd__unused,
726744
return 0;
727745
}
728746

729-
static int ll_binary_merge(const char *cmd__unused,
747+
static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
748+
const char *path_unused,
730749
mmfile_t *orig,
731750
mmfile_t *src1, const char *name1,
732751
mmfile_t *src2, const char *name2,
@@ -745,14 +764,13 @@ static int ll_binary_merge(const char *cmd__unused,
745764
return 1;
746765
}
747766

748-
static struct {
749-
const char *name;
750-
ll_merge_fn fn;
751-
} ll_merge_fns[] = {
752-
{ "binary", ll_binary_merge },
753-
{ "text", ll_xdl_merge },
754-
{ "union", ll_union_merge },
755-
{ NULL, NULL },
767+
#define LL_BINARY_MERGE 0
768+
#define LL_TEXT_MERGE 1
769+
#define LL_UNION_MERGE 2
770+
static struct ll_merge_driver ll_merge_drv[] = {
771+
{ "binary", "built-in binary merge", ll_binary_merge },
772+
{ "text", "built-in 3-way text merge", ll_xdl_merge },
773+
{ "union", "built-in union merge", ll_union_merge },
756774
};
757775

758776
static void create_temp(mmfile_t *src, char *path)
@@ -768,7 +786,11 @@ static void create_temp(mmfile_t *src, char *path)
768786
close(fd);
769787
}
770788

771-
static int ll_ext_merge(const char *cmd,
789+
/*
790+
* User defined low-level merge driver support.
791+
*/
792+
static int ll_ext_merge(const struct ll_merge_driver *fn,
793+
const char *path,
772794
mmfile_t *orig,
773795
mmfile_t *src1, const char *name1,
774796
mmfile_t *src2, const char *name2,
@@ -796,7 +818,10 @@ static int ll_ext_merge(const char *cmd,
796818
interp_set_entry(table, 1, temp[1]);
797819
interp_set_entry(table, 2, temp[2]);
798820

799-
interpolate(cmdbuf, sizeof(cmdbuf), cmd, table, 3);
821+
output(1, "merging %s using %s", path,
822+
fn->description ? fn->description : fn->name);
823+
824+
interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
800825

801826
memset(&child, 0, sizeof(child));
802827
child.argv = args;
@@ -833,101 +858,124 @@ static int ll_ext_merge(const char *cmd,
833858
/*
834859
* merge.default and merge.driver configuration items
835860
*/
836-
static struct user_merge_fn {
837-
struct user_merge_fn *next;
838-
const char *name;
839-
char *cmdline;
840-
char b_[1];
841-
} *ll_user_merge_fns, **ll_user_merge_fns_tail;
861+
static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
842862
static const char *default_ll_merge;
843863

844864
static int read_merge_config(const char *var, const char *value)
845865
{
846-
struct user_merge_fn *fn;
847-
int blen, nlen;
866+
struct ll_merge_driver *fn;
867+
const char *ep, *name;
868+
int namelen;
848869

849870
if (!strcmp(var, "merge.default")) {
850-
default_ll_merge = strdup(value);
871+
if (value)
872+
default_ll_merge = strdup(value);
851873
return 0;
852874
}
853875

854-
if (strcmp(var, "merge.driver"))
876+
/*
877+
* We are not interested in anything but "merge.<name>.variable";
878+
* especially, we do not want to look at variables such as
879+
* "merge.summary", "merge.tool", and "merge.verbosity".
880+
*/
881+
if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 6)
855882
return 0;
856-
if (!value)
857-
return error("%s: lacks value", var);
883+
858884
/*
859-
* merge.driver is a multi-valued configuration, whose value is
860-
* of form:
861-
*
862-
* name command-line
863-
*
864-
* The command-line will be interpolated with the following
865-
* tokens and is given to the shell:
866-
*
867-
* %O - temporary file name for the merge base.
868-
* %A - temporary file name for our version.
869-
* %B - temporary file name for the other branches' version.
870-
*
871-
* The external merge driver should write the results in the file
872-
* named by %A, and signal that it has done with exit status 0.
885+
* Find existing one as we might be processing merge.<name>.var2
886+
* after seeing merge.<name>.var1.
873887
*/
874-
for (nlen = -1, blen = 0; value[blen]; blen++)
875-
if (nlen < 0 && isspace(value[blen]))
876-
nlen = blen;
877-
if (nlen < 0)
878-
return error("%s '%s': lacks command line", var, value);
879-
fn = xcalloc(1, sizeof(struct user_merge_fn) + blen + 1);
880-
memcpy(fn->b_, value, blen + 1);
881-
fn->name = fn->b_;
882-
fn->b_[nlen] = 0;
883-
fn->cmdline = fn->b_ + nlen + 1;
884-
fn->next = *ll_user_merge_fns_tail;
885-
*ll_user_merge_fns_tail = fn;
888+
name = var + 6;
889+
namelen = ep - name;
890+
for (fn = ll_user_merge; fn; fn = fn->next)
891+
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
892+
break;
893+
if (!fn) {
894+
char *namebuf;
895+
fn = xcalloc(1, sizeof(struct ll_merge_driver));
896+
namebuf = xmalloc(namelen + 1);
897+
memcpy(namebuf, name, namelen);
898+
namebuf[namelen] = 0;
899+
fn->name = namebuf;
900+
fn->fn = ll_ext_merge;
901+
fn->next = *ll_user_merge_tail;
902+
*ll_user_merge_tail = fn;
903+
}
904+
905+
ep++;
906+
907+
if (!strcmp("name", ep)) {
908+
if (!value)
909+
return error("%s: lacks value", var);
910+
fn->description = strdup(value);
911+
return 0;
912+
}
913+
914+
if (!strcmp("driver", ep)) {
915+
if (!value)
916+
return error("%s: lacks value", var);
917+
/*
918+
* merge.<name>.driver specifies the command line:
919+
*
920+
* command-line
921+
*
922+
* The command-line will be interpolated with the following
923+
* tokens and is given to the shell:
924+
*
925+
* %O - temporary file name for the merge base.
926+
* %A - temporary file name for our version.
927+
* %B - temporary file name for the other branches' version.
928+
*
929+
* The external merge driver should write the results in the
930+
* file named by %A, and signal that it has done with zero exit
931+
* status.
932+
*/
933+
fn->cmdline = strdup(value);
934+
return 0;
935+
}
936+
886937
return 0;
887938
}
888939

889940
static void initialize_ll_merge(void)
890941
{
891-
if (ll_user_merge_fns_tail)
942+
if (ll_user_merge_tail)
892943
return;
893-
ll_user_merge_fns_tail = &ll_user_merge_fns;
944+
ll_user_merge_tail = &ll_user_merge;
894945
git_config(read_merge_config);
895946
}
896947

897-
static ll_merge_fn find_ll_merge_fn(void *merge_attr, const char **cmdline)
948+
static const struct ll_merge_driver *find_ll_merge_driver(void *merge_attr)
898949
{
899-
struct user_merge_fn *fn;
950+
struct ll_merge_driver *fn;
900951
const char *name;
901952
int i;
902953

903954
initialize_ll_merge();
904955

905956
if (ATTR_TRUE(merge_attr))
906-
return ll_xdl_merge;
957+
return &ll_merge_drv[LL_TEXT_MERGE];
907958
else if (ATTR_FALSE(merge_attr))
908-
return ll_binary_merge;
959+
return &ll_merge_drv[LL_BINARY_MERGE];
909960
else if (ATTR_UNSET(merge_attr)) {
910961
if (!default_ll_merge)
911-
return ll_xdl_merge;
962+
return &ll_merge_drv[LL_TEXT_MERGE];
912963
else
913964
name = default_ll_merge;
914965
}
915966
else
916967
name = merge_attr;
917968

918-
for (fn = ll_user_merge_fns; fn; fn = fn->next) {
919-
if (!strcmp(fn->name, name)) {
920-
*cmdline = fn->cmdline;
921-
return ll_ext_merge;
922-
}
923-
}
969+
for (fn = ll_user_merge; fn; fn = fn->next)
970+
if (!strcmp(fn->name, name))
971+
return fn;
924972

925-
for (i = 0; ll_merge_fns[i].name; i++)
926-
if (!strcmp(ll_merge_fns[i].name, name))
927-
return ll_merge_fns[i].fn;
973+
for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
974+
if (!strcmp(ll_merge_drv[i].name, name))
975+
return &ll_merge_drv[i];
928976

929977
/* default to the 3-way */
930-
return ll_xdl_merge;
978+
return &ll_merge_drv[LL_TEXT_MERGE];
931979
}
932980

933981
static void *git_path_check_merge(const char *path)
@@ -953,8 +1001,7 @@ static int ll_merge(mmbuffer_t *result_buf,
9531001
char *name1, *name2;
9541002
int merge_status;
9551003
void *merge_attr;
956-
ll_merge_fn fn;
957-
const char *driver = NULL;
1004+
const struct ll_merge_driver *driver;
9581005

9591006
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
9601007
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -964,10 +1011,11 @@ static int ll_merge(mmbuffer_t *result_buf,
9641011
fill_mm(b->sha1, &src2);
9651012

9661013
merge_attr = git_path_check_merge(a->path);
967-
fn = find_ll_merge_fn(merge_attr, &driver);
1014+
driver = find_ll_merge_driver(merge_attr);
9681015

969-
merge_status = fn(driver, &orig,
970-
&src1, name1, &src2, name2, result_buf);
1016+
merge_status = driver->fn(driver, a->path,
1017+
&orig, &src1, name1, &src2, name2,
1018+
result_buf);
9711019

9721020
free(name1);
9731021
free(name2);

t/t6026-merge-attr.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ test_expect_success 'custom merge backend' '
9898
9999
git reset --hard anchor &&
100100
git config --replace-all \
101-
merge.driver "custom ./custom-merge %O %A %B 0" &&
101+
merge.custom.driver "./custom-merge %O %A %B 0" &&
102+
git config --replace-all \
103+
merge.custom.name "custom merge driver for testing" &&
102104
103105
git merge master &&
104106
@@ -117,7 +119,9 @@ test_expect_success 'custom merge backend' '
117119
118120
git reset --hard anchor &&
119121
git config --replace-all \
120-
merge.driver "custom ./custom-merge %O %A %B 1" &&
122+
merge.custom.driver "./custom-merge %O %A %B 1" &&
123+
git config --replace-all \
124+
merge.custom.name "custom merge driver for testing" &&
121125
122126
if git merge master
123127
then

0 commit comments

Comments
 (0)