Skip to content

Commit

Permalink
ll-merge: replace flag argument with options struct
Browse files Browse the repository at this point in the history
Keeping track of the flag bits is proving more trouble than it's
worth.  Instead, use a pointer to an options struct like most similar
APIs do.

Callers with no special requests can pass NULL to request the default
options.

Cc: Bert Wesarg <bert.wesarg@googlemail.com>
Cc: Avery Pennarun <apenwarr@gmail.com>
Helped-by: Justin Frankel <justin@cockos.com>
Helped-by: Bert Wesarg <bert.wesarg@googlemail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Jonathan Nieder authored and Junio C Hamano committed Aug 26, 2010
1 parent 635a7bb commit 712516b
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 66 deletions.
71 changes: 51 additions & 20 deletions Documentation/technical/api-merge.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,40 @@ responsible for a few things.
path-specific merge drivers (specified in `.gitattributes`)
into account.

Data structures
---------------

* `mmbuffer_t`, `mmfile_t`

These store data usable for use by the xdiff backend, for writing and
for reading, respectively. See `xdiff/xdiff.h` for the definitions
and `diff.c` for examples.

* `struct ll_merge_options`

This describes the set of options the calling program wants to affect
the operation of a low-level (single file) merge. Some options:

`virtual_ancestor`::
Behave as though this were part of a merge between common
ancestors in a recursive merge.
If a helper program is specified by the
`[merge "<driver>"] recursive` configuration, it will
be used (see linkgit:gitattributes[5]).

`variant`::
Resolve local conflicts automatically in favor
of one side or the other (as in 'git merge-file'
`--ours`/`--theirs`/`--union`). Can be `0`,
`XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
`XDL_MERGE_FAVOR_UNION`.

`renormalize`::
Resmudge and clean the "base", "theirs" and "ours" files
before merging. Use this when the merge is likely to have
overlapped with a change in smudge/clean or end-of-line
normalization rules.

Low-level (single file) merge
-----------------------------

Expand All @@ -28,15 +62,24 @@ Low-level (single file) merge
`.git/info/attributes` into account. Returns 0 for a
clean merge.

The caller:
Calling sequence:

1. allocates an mmbuffer_t variable for the result;
2. allocates and fills variables with the file's original content
and two modified versions (using `read_mmfile`, for example);
3. calls ll_merge();
4. reads the output from result_buf.ptr and result_buf.size;
5. releases buffers when finished (free(ancestor.ptr); free(ours.ptr);
free(theirs.ptr); free(result_buf.ptr);).
* Prepare a `struct ll_merge_options` to record options.
If you have no special requests, skip this and pass `NULL`
as the `opts` parameter to use the default options.

* Allocate an mmbuffer_t variable for the result.

* Allocate and fill variables with the file's original content
and two modified versions (using `read_mmfile`, for example).

* Call `ll_merge()`.

* Read the merged content from `result_buf.ptr` and `result_buf.size`.

* Release buffers when finished. A simple
`free(ancestor.ptr); free(ours.ptr); free(theirs.ptr);
free(result_buf.ptr);` will do.

If the modifications do not merge cleanly, `ll_merge` will return a
nonzero value and `result_buf` will generally include a description of
Expand All @@ -47,18 +90,6 @@ The `ancestor_label`, `our_label`, and `their_label` parameters are
used to label the different sides of a conflict if the merge driver
supports this.

The `flag` parameter is a bitfield:

- The `LL_OPT_VIRTUAL_ANCESTOR` bit indicates whether this is an
internal merge to consolidate ancestors for a recursive merge.

- The `LL_OPT_FAVOR_MASK` bits allow local conflicts to be automatically
resolved in favor of one side or the other (as in 'git merge-file'
`--ours`/`--theirs`/`--union`).
They can be populated by `create_ll_flag`, whose argument can be
`XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
`XDL_MERGE_FAVOR_UNION`.

Everything else
---------------

Expand Down
2 changes: 1 addition & 1 deletion builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ static int checkout_merged(int pos, struct checkout *state)
* merge.renormalize set, too
*/
status = ll_merge(&result_buf, path, &ancestor, "base",
&ours, "ours", &theirs, "theirs", 0);
&ours, "ours", &theirs, "theirs", NULL);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
Expand Down
50 changes: 33 additions & 17 deletions ll-merge.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int flag,
const struct ll_merge_options *opts,
int marker_size);

struct ll_merge_driver {
Expand All @@ -39,14 +39,18 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int flag, int marker_size)
const struct ll_merge_options *opts,
int marker_size)
{
mmfile_t *stolen;
assert(opts);

/*
* The tentative merge result is "ours" for the final round,
* or common ancestor for an internal merge. Still return
* "conflicted merge" status.
*/
mmfile_t *stolen = (flag & LL_OPT_VIRTUAL_ANCESTOR) ? orig : src1;
stolen = opts->virtual_ancestor ? orig : src1;

result->ptr = stolen->ptr;
result->size = stolen->size;
Expand All @@ -60,9 +64,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int flag, int marker_size)
const struct ll_merge_options *opts,
int marker_size)
{
xmparam_t xmp;
assert(opts);

if (buffer_is_binary(orig->ptr, orig->size) ||
buffer_is_binary(src1->ptr, src1->size) ||
Expand All @@ -74,12 +80,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
orig, orig_name,
src1, name1,
src2, name2,
flag, marker_size);
opts, marker_size);
}

memset(&xmp, 0, sizeof(xmp));
xmp.level = XDL_MERGE_ZEALOUS;
xmp.favor = ll_opt_favor(flag);
xmp.favor = opts->variant;
if (git_xmerge_style >= 0)
xmp.style = git_xmerge_style;
if (marker_size > 0)
Expand All @@ -96,15 +102,17 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int flag, int marker_size)
const struct ll_merge_options *opts,
int marker_size)
{
/* Use union favor */
flag &= ~LL_OPT_FAVOR_MASK;
flag |= create_ll_flag(XDL_MERGE_FAVOR_UNION);
struct ll_merge_options o;
assert(opts);
o = *opts;
o.variant = XDL_MERGE_FAVOR_UNION;
return ll_xdl_merge(drv_unused, result, path_unused,
orig, NULL, src1, NULL, src2, NULL,
flag, marker_size);
return 0;
&o, marker_size);
}

#define LL_BINARY_MERGE 0
Expand Down Expand Up @@ -136,14 +144,16 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
int flag, int marker_size)
const struct ll_merge_options *opts,
int marker_size)
{
char temp[4][50];
struct strbuf cmd = STRBUF_INIT;
struct strbuf_expand_dict_entry dict[5];
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
assert(opts);

dict[0].placeholder = "O"; dict[0].value = temp[0];
dict[1].placeholder = "A"; dict[1].value = temp[1];
Expand Down Expand Up @@ -337,15 +347,21 @@ int ll_merge(mmbuffer_t *result_buf,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
int flag)
const struct ll_merge_options *opts)
{
static struct git_attr_check check[2];
const char *ll_driver_name = NULL;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
const struct ll_merge_driver *driver;
int virtual_ancestor = flag & LL_OPT_VIRTUAL_ANCESTOR;

if (flag & LL_OPT_RENORMALIZE) {
if (!opts) {
struct ll_merge_options default_opts = {0};
return ll_merge(result_buf, path, ancestor, ancestor_label,
ours, our_label, theirs, their_label,
&default_opts);
}

if (opts->renormalize) {
normalize_file(ancestor, path);
normalize_file(ours, path);
normalize_file(theirs, path);
Expand All @@ -359,11 +375,11 @@ int ll_merge(mmbuffer_t *result_buf,
}
}
driver = find_ll_merge_driver(ll_driver_name);
if (virtual_ancestor && driver->recursive)
if (opts->virtual_ancestor && driver->recursive)
driver = find_ll_merge_driver(driver->recursive);
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
ours, our_label, theirs, their_label,
flag, marker_size);
opts, marker_size);
}

int ll_merge_marker_size(const char *path)
Expand Down
21 changes: 6 additions & 15 deletions ll-merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,18 @@
#ifndef LL_MERGE_H
#define LL_MERGE_H

#define LL_OPT_VIRTUAL_ANCESTOR (1 << 0)
#define LL_OPT_FAVOR_MASK ((1 << 1) | (1 << 2))
#define LL_OPT_FAVOR_SHIFT 1
#define LL_OPT_RENORMALIZE (1 << 3)

static inline int ll_opt_favor(int flag)
{
return (flag & LL_OPT_FAVOR_MASK) >> LL_OPT_FAVOR_SHIFT;
}

static inline int create_ll_flag(int favor)
{
return ((favor << LL_OPT_FAVOR_SHIFT) & LL_OPT_FAVOR_MASK);
}
struct ll_merge_options {
unsigned virtual_ancestor : 1;
unsigned variant : 2; /* favor ours, favor theirs, or union merge */
unsigned renormalize : 1;
};

int ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
int flag);
const struct ll_merge_options *opts);

int ll_merge_marker_size(const char *path);

Expand Down
2 changes: 1 addition & 1 deletion merge-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our
* common ancestor.
*/
merge_status = ll_merge(&res, path, base, NULL,
our, ".our", their, ".their", 0);
our, ".our", their, ".their", NULL);
if (merge_status < 0)
return NULL;

Expand Down
22 changes: 11 additions & 11 deletions merge-recursive.c
Original file line number Diff line number Diff line change
Expand Up @@ -608,22 +608,25 @@ static int merge_3way(struct merge_options *o,
const char *branch2)
{
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
char *base_name, *name1, *name2;
int merge_status;
int favor;

if (o->call_depth)
favor = 0;
else {
ll_opts.renormalize = o->renormalize;

if (o->call_depth) {
ll_opts.virtual_ancestor = 1;
ll_opts.variant = 0;
} else {
switch (o->recursive_variant) {
case MERGE_RECURSIVE_OURS:
favor = XDL_MERGE_FAVOR_OURS;
ll_opts.variant = XDL_MERGE_FAVOR_OURS;
break;
case MERGE_RECURSIVE_THEIRS:
favor = XDL_MERGE_FAVOR_THEIRS;
ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
break;
default:
favor = 0;
ll_opts.variant = 0;
break;
}
}
Expand All @@ -646,10 +649,7 @@ static int merge_3way(struct merge_options *o,
read_mmblob(&src2, b->sha1);

merge_status = ll_merge(result_buf, a->path, &orig, base_name,
&src1, name1, &src2, name2,
((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
(o->renormalize ? LL_OPT_RENORMALIZE : 0) |
create_ll_flag(favor)));
&src1, name1, &src2, name2, &ll_opts);

free(name1);
free(name2);
Expand Down
2 changes: 1 addition & 1 deletion rerere.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
*/
ll_merge(&result, path, &mmfile[0], NULL,
&mmfile[1], "ours",
&mmfile[2], "theirs", 0);
&mmfile[2], "theirs", NULL);
for (i = 0; i < 3; i++)
free(mmfile[i].ptr);

Expand Down

0 comments on commit 712516b

Please sign in to comment.