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:
parent
be89cb239e
commit
153920da5b
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user