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>
This commit is contained in:
@ -661,14 +661,31 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
|
|||||||
mm->size = size;
|
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 *orig,
|
||||||
mmfile_t *src1, const char *name1,
|
mmfile_t *src1, const char *name1,
|
||||||
mmfile_t *src2, const char *name2,
|
mmfile_t *src2, const char *name2,
|
||||||
mmbuffer_t *result);
|
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 *orig,
|
||||||
mmfile_t *src1, const char *name1,
|
mmfile_t *src1, const char *name1,
|
||||||
mmfile_t *src2, const char *name2,
|
mmfile_t *src2, const char *name2,
|
||||||
@ -684,7 +701,8 @@ static int ll_xdl_merge(const char *cmd__unused,
|
|||||||
result);
|
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 *orig,
|
||||||
mmfile_t *src1, const char *name1,
|
mmfile_t *src1, const char *name1,
|
||||||
mmfile_t *src2, const char *name2,
|
mmfile_t *src2, const char *name2,
|
||||||
@ -694,8 +712,8 @@ static int ll_union_merge(const char *cmd__unused,
|
|||||||
long size;
|
long size;
|
||||||
const int marker_size = 7;
|
const int marker_size = 7;
|
||||||
|
|
||||||
int status = ll_xdl_merge(cmd__unused, orig,
|
int status = ll_xdl_merge(drv_unused, path_unused,
|
||||||
src1, NULL, src2, NULL, result);
|
orig, src1, NULL, src2, NULL, result);
|
||||||
if (status <= 0)
|
if (status <= 0)
|
||||||
return status;
|
return status;
|
||||||
size = result->size;
|
size = result->size;
|
||||||
@ -726,7 +744,8 @@ static int ll_union_merge(const char *cmd__unused,
|
|||||||
return 0;
|
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 *orig,
|
||||||
mmfile_t *src1, const char *name1,
|
mmfile_t *src1, const char *name1,
|
||||||
mmfile_t *src2, const char *name2,
|
mmfile_t *src2, const char *name2,
|
||||||
@ -745,14 +764,13 @@ static int ll_binary_merge(const char *cmd__unused,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct {
|
#define LL_BINARY_MERGE 0
|
||||||
const char *name;
|
#define LL_TEXT_MERGE 1
|
||||||
ll_merge_fn fn;
|
#define LL_UNION_MERGE 2
|
||||||
} ll_merge_fns[] = {
|
static struct ll_merge_driver ll_merge_drv[] = {
|
||||||
{ "binary", ll_binary_merge },
|
{ "binary", "built-in binary merge", ll_binary_merge },
|
||||||
{ "text", ll_xdl_merge },
|
{ "text", "built-in 3-way text merge", ll_xdl_merge },
|
||||||
{ "union", ll_union_merge },
|
{ "union", "built-in union merge", ll_union_merge },
|
||||||
{ NULL, NULL },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void create_temp(mmfile_t *src, char *path)
|
static void create_temp(mmfile_t *src, char *path)
|
||||||
@ -768,7 +786,11 @@ static void create_temp(mmfile_t *src, char *path)
|
|||||||
close(fd);
|
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 *orig,
|
||||||
mmfile_t *src1, const char *name1,
|
mmfile_t *src1, const char *name1,
|
||||||
mmfile_t *src2, const char *name2,
|
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, 1, temp[1]);
|
||||||
interp_set_entry(table, 2, temp[2]);
|
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));
|
memset(&child, 0, sizeof(child));
|
||||||
child.argv = args;
|
child.argv = args;
|
||||||
@ -833,101 +858,124 @@ static int ll_ext_merge(const char *cmd,
|
|||||||
/*
|
/*
|
||||||
* merge.default and merge.driver configuration items
|
* merge.default and merge.driver configuration items
|
||||||
*/
|
*/
|
||||||
static struct user_merge_fn {
|
static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
|
||||||
struct user_merge_fn *next;
|
|
||||||
const char *name;
|
|
||||||
char *cmdline;
|
|
||||||
char b_[1];
|
|
||||||
} *ll_user_merge_fns, **ll_user_merge_fns_tail;
|
|
||||||
static const char *default_ll_merge;
|
static const char *default_ll_merge;
|
||||||
|
|
||||||
static int read_merge_config(const char *var, const char *value)
|
static int read_merge_config(const char *var, const char *value)
|
||||||
{
|
{
|
||||||
struct user_merge_fn *fn;
|
struct ll_merge_driver *fn;
|
||||||
int blen, nlen;
|
const char *ep, *name;
|
||||||
|
int namelen;
|
||||||
|
|
||||||
if (!strcmp(var, "merge.default")) {
|
if (!strcmp(var, "merge.default")) {
|
||||||
default_ll_merge = strdup(value);
|
if (value)
|
||||||
|
default_ll_merge = strdup(value);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* We are not interested in anything but "merge.<name>.variable";
|
||||||
* of form:
|
* especially, we do not want to look at variables such as
|
||||||
*
|
* "merge.summary", "merge.tool", and "merge.verbosity".
|
||||||
* 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 (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 6)
|
||||||
if (nlen < 0 && isspace(value[blen]))
|
return 0;
|
||||||
nlen = blen;
|
|
||||||
if (nlen < 0)
|
/*
|
||||||
return error("%s '%s': lacks command line", var, value);
|
* Find existing one as we might be processing merge.<name>.var2
|
||||||
fn = xcalloc(1, sizeof(struct user_merge_fn) + blen + 1);
|
* after seeing merge.<name>.var1.
|
||||||
memcpy(fn->b_, value, blen + 1);
|
*/
|
||||||
fn->name = fn->b_;
|
name = var + 6;
|
||||||
fn->b_[nlen] = 0;
|
namelen = ep - name;
|
||||||
fn->cmdline = fn->b_ + nlen + 1;
|
for (fn = ll_user_merge; fn; fn = fn->next)
|
||||||
fn->next = *ll_user_merge_fns_tail;
|
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
|
||||||
*ll_user_merge_fns_tail = fn;
|
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.<name>.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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void initialize_ll_merge(void)
|
static void initialize_ll_merge(void)
|
||||||
{
|
{
|
||||||
if (ll_user_merge_fns_tail)
|
if (ll_user_merge_tail)
|
||||||
return;
|
return;
|
||||||
ll_user_merge_fns_tail = &ll_user_merge_fns;
|
ll_user_merge_tail = &ll_user_merge;
|
||||||
git_config(read_merge_config);
|
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;
|
const char *name;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
initialize_ll_merge();
|
initialize_ll_merge();
|
||||||
|
|
||||||
if (ATTR_TRUE(merge_attr))
|
if (ATTR_TRUE(merge_attr))
|
||||||
return ll_xdl_merge;
|
return &ll_merge_drv[LL_TEXT_MERGE];
|
||||||
else if (ATTR_FALSE(merge_attr))
|
else if (ATTR_FALSE(merge_attr))
|
||||||
return ll_binary_merge;
|
return &ll_merge_drv[LL_BINARY_MERGE];
|
||||||
else if (ATTR_UNSET(merge_attr)) {
|
else if (ATTR_UNSET(merge_attr)) {
|
||||||
if (!default_ll_merge)
|
if (!default_ll_merge)
|
||||||
return ll_xdl_merge;
|
return &ll_merge_drv[LL_TEXT_MERGE];
|
||||||
else
|
else
|
||||||
name = default_ll_merge;
|
name = default_ll_merge;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
name = merge_attr;
|
name = merge_attr;
|
||||||
|
|
||||||
for (fn = ll_user_merge_fns; fn; fn = fn->next) {
|
for (fn = ll_user_merge; fn; fn = fn->next)
|
||||||
if (!strcmp(fn->name, name)) {
|
if (!strcmp(fn->name, name))
|
||||||
*cmdline = fn->cmdline;
|
return fn;
|
||||||
return ll_ext_merge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; ll_merge_fns[i].name; i++)
|
for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
|
||||||
if (!strcmp(ll_merge_fns[i].name, name))
|
if (!strcmp(ll_merge_drv[i].name, name))
|
||||||
return ll_merge_fns[i].fn;
|
return &ll_merge_drv[i];
|
||||||
|
|
||||||
/* default to the 3-way */
|
/* 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)
|
static void *git_path_check_merge(const char *path)
|
||||||
@ -953,8 +1001,7 @@ static int ll_merge(mmbuffer_t *result_buf,
|
|||||||
char *name1, *name2;
|
char *name1, *name2;
|
||||||
int merge_status;
|
int merge_status;
|
||||||
void *merge_attr;
|
void *merge_attr;
|
||||||
ll_merge_fn fn;
|
const struct ll_merge_driver *driver;
|
||||||
const char *driver = NULL;
|
|
||||||
|
|
||||||
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
|
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
|
||||||
name2 = xstrdup(mkpath("%s:%s", branch2, b->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);
|
fill_mm(b->sha1, &src2);
|
||||||
|
|
||||||
merge_attr = git_path_check_merge(a->path);
|
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,
|
merge_status = driver->fn(driver, a->path,
|
||||||
&src1, name1, &src2, name2, result_buf);
|
&orig, &src1, name1, &src2, name2,
|
||||||
|
result_buf);
|
||||||
|
|
||||||
free(name1);
|
free(name1);
|
||||||
free(name2);
|
free(name2);
|
||||||
|
@ -98,7 +98,9 @@ test_expect_success 'custom merge backend' '
|
|||||||
|
|
||||||
git reset --hard anchor &&
|
git reset --hard anchor &&
|
||||||
git config --replace-all \
|
git config --replace-all \
|
||||||
merge.driver "custom ./custom-merge %O %A %B 0" &&
|
merge.custom.driver "./custom-merge %O %A %B 0" &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.name "custom merge driver for testing" &&
|
||||||
|
|
||||||
git merge master &&
|
git merge master &&
|
||||||
|
|
||||||
@ -117,7 +119,9 @@ test_expect_success 'custom merge backend' '
|
|||||||
|
|
||||||
git reset --hard anchor &&
|
git reset --hard anchor &&
|
||||||
git config --replace-all \
|
git config --replace-all \
|
||||||
merge.driver "custom ./custom-merge %O %A %B 1" &&
|
merge.custom.driver "./custom-merge %O %A %B 1" &&
|
||||||
|
git config --replace-all \
|
||||||
|
merge.custom.name "custom merge driver for testing" &&
|
||||||
|
|
||||||
if git merge master
|
if git merge master
|
||||||
then
|
then
|
||||||
|
Reference in New Issue
Block a user