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:
Junio C Hamano 2007-04-18 11:27:32 -07:00
parent be89cb239e
commit 153920da5b
2 changed files with 132 additions and 80 deletions

View File

@ -661,14 +661,31 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
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 *src1, const char *name1,
mmfile_t *src2, const char *name2,
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 *src1, const char *name1,
mmfile_t *src2, const char *name2,
@ -684,7 +701,8 @@ static int ll_xdl_merge(const char *cmd__unused,
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 *src1, const char *name1,
mmfile_t *src2, const char *name2,
@ -694,8 +712,8 @@ static int ll_union_merge(const char *cmd__unused,
long size;
const int marker_size = 7;
int status = ll_xdl_merge(cmd__unused, orig,
src1, NULL, src2, NULL, result);
int status = ll_xdl_merge(drv_unused, path_unused,
orig, src1, NULL, src2, NULL, result);
if (status <= 0)
return status;
size = result->size;
@ -726,7 +744,8 @@ static int ll_union_merge(const char *cmd__unused,
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 *src1, const char *name1,
mmfile_t *src2, const char *name2,
@ -745,14 +764,13 @@ static int ll_binary_merge(const char *cmd__unused,
return 1;
}
static struct {
const char *name;
ll_merge_fn fn;
} ll_merge_fns[] = {
{ "binary", ll_binary_merge },
{ "text", ll_xdl_merge },
{ "union", ll_union_merge },
{ NULL, NULL },
#define LL_BINARY_MERGE 0
#define LL_TEXT_MERGE 1
#define LL_UNION_MERGE 2
static struct ll_merge_driver ll_merge_drv[] = {
{ "binary", "built-in binary merge", ll_binary_merge },
{ "text", "built-in 3-way text merge", ll_xdl_merge },
{ "union", "built-in union merge", ll_union_merge },
};
static void create_temp(mmfile_t *src, char *path)
@ -768,7 +786,11 @@ static void create_temp(mmfile_t *src, char *path)
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 *src1, const char *name1,
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, 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));
child.argv = args;
@ -833,101 +858,124 @@ static int ll_ext_merge(const char *cmd,
/*
* 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 struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
static const char *default_ll_merge;
static int read_merge_config(const char *var, const char *value)
{
struct user_merge_fn *fn;
int blen, nlen;
struct ll_merge_driver *fn;
const char *ep, *name;
int namelen;
if (!strcmp(var, "merge.default")) {
default_ll_merge = strdup(value);
if (value)
default_ll_merge = strdup(value);
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
* 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.
* We are not interested in anything but "merge.<name>.variable";
* especially, we do not want to look at variables such as
* "merge.summary", "merge.tool", and "merge.verbosity".
*/
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;
if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 6)
return 0;
/*
* Find existing one as we might be processing merge.<name>.var2
* after seeing merge.<name>.var1.
*/
name = var + 6;
namelen = ep - name;
for (fn = ll_user_merge; fn; fn = fn->next)
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
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;
}
static void initialize_ll_merge(void)
{
if (ll_user_merge_fns_tail)
if (ll_user_merge_tail)
return;
ll_user_merge_fns_tail = &ll_user_merge_fns;
ll_user_merge_tail = &ll_user_merge;
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;
int i;
initialize_ll_merge();
if (ATTR_TRUE(merge_attr))
return ll_xdl_merge;
return &ll_merge_drv[LL_TEXT_MERGE];
else if (ATTR_FALSE(merge_attr))
return ll_binary_merge;
return &ll_merge_drv[LL_BINARY_MERGE];
else if (ATTR_UNSET(merge_attr)) {
if (!default_ll_merge)
return ll_xdl_merge;
return &ll_merge_drv[LL_TEXT_MERGE];
else
name = default_ll_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;
}
}
for (fn = ll_user_merge; fn; fn = fn->next)
if (!strcmp(fn->name, name))
return fn;
for (i = 0; ll_merge_fns[i].name; i++)
if (!strcmp(ll_merge_fns[i].name, name))
return ll_merge_fns[i].fn;
for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
if (!strcmp(ll_merge_drv[i].name, name))
return &ll_merge_drv[i];
/* 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)
@ -953,8 +1001,7 @@ static int ll_merge(mmbuffer_t *result_buf,
char *name1, *name2;
int merge_status;
void *merge_attr;
ll_merge_fn fn;
const char *driver = NULL;
const struct ll_merge_driver *driver;
name1 = xstrdup(mkpath("%s:%s", branch1, a->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);
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,
&src1, name1, &src2, name2, result_buf);
merge_status = driver->fn(driver, a->path,
&orig, &src1, name1, &src2, name2,
result_buf);
free(name1);
free(name2);

View File

@ -98,7 +98,9 @@ test_expect_success 'custom merge backend' '
git reset --hard anchor &&
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 &&
@ -117,7 +119,9 @@ test_expect_success 'custom merge backend' '
git reset --hard anchor &&
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
then