Skip to content

Commit

Permalink
Merge branch 'jf/merge-ignore-ws'
Browse files Browse the repository at this point in the history
* jf/merge-ignore-ws:
  merge-recursive: options to ignore whitespace changes
  merge-recursive --patience
  ll-merge: replace flag argument with options struct
  merge-recursive: expose merge options for builtin merge
  • Loading branch information
gitster committed Oct 27, 2010
2 parents 5a3a484 + 4e5dd04 commit 75b17fe
Show file tree
Hide file tree
Showing 12 changed files with 351 additions and 97 deletions.
22 changes: 22 additions & 0 deletions Documentation/merge-strategies.txt
Expand Up @@ -40,6 +40,28 @@ the other tree did, declaring 'our' history contains all that happened in it.
theirs;;
This is opposite of 'ours'.

patience;;
With this option, 'merge-recursive' spends a little extra time
to avoid mismerges that sometimes occur due to unimportant
matching lines (e.g., braces from distinct functions). Use
this when the branches to be merged have diverged wildly.
See also linkgit:git-diff[1] `--patience`.

ignore-space-change;;
ignore-all-space;;
ignore-space-at-eol;;
Treats lines with the indicated type of whitespace change as
unchanged for the sake of a three-way merge. Whitespace
changes mixed with other changes to a line are not ignored.
See also linkgit:git-diff[1] `-b`, `-w`, and
`--ignore-space-at-eol`.
+
* If 'their' version only introduces whitespace changes to a line,
'our' version is used;
* If 'our' version introduces whitespace changes but 'their'
version includes a substantial change, 'their' version is used;
* Otherwise, the merge proceeds in the usual way.

renormalize;;
This runs a virtual check-out and check-in of all three stages
of a file when resolving a three-way merge. This option is
Expand Down
71 changes: 51 additions & 20 deletions Documentation/technical/api-merge.txt
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
Expand Up @@ -161,7 +161,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
15 changes: 2 additions & 13 deletions builtin/merge-recursive.c
Expand Up @@ -2,6 +2,7 @@
#include "commit.h"
#include "tag.h"
#include "merge-recursive.h"
#include "xdiff-interface.h"

static const char builtin_merge_recursive_usage[] =
"git %s <base>... -- <head> <remote> ...";
Expand Down Expand Up @@ -40,19 +41,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (!prefixcmp(arg, "--")) {
if (!arg[2])
break;
if (!strcmp(arg+2, "ours"))
o.recursive_variant = MERGE_RECURSIVE_OURS;
else if (!strcmp(arg+2, "theirs"))
o.recursive_variant = MERGE_RECURSIVE_THEIRS;
else if (!strcmp(arg+2, "subtree"))
o.subtree_shift = "";
else if (!prefixcmp(arg+2, "subtree="))
o.subtree_shift = arg + 10;
else if (!strcmp(arg+2, "renormalize"))
o.renormalize = 1;
else if (!strcmp(arg+2, "no-renormalize"))
o.renormalize = 0;
else
if (parse_merge_opt(&o, arg + 2))
die("Unknown option %s", arg);
continue;
}
Expand Down
20 changes: 2 additions & 18 deletions builtin/merge.c
Expand Up @@ -639,25 +639,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,

o.renormalize = option_renormalize;

/*
* NEEDSWORK: merge with table in builtin/merge-recursive
*/
for (x = 0; x < xopts_nr; x++) {
if (!strcmp(xopts[x], "ours"))
o.recursive_variant = MERGE_RECURSIVE_OURS;
else if (!strcmp(xopts[x], "theirs"))
o.recursive_variant = MERGE_RECURSIVE_THEIRS;
else if (!strcmp(xopts[x], "subtree"))
o.subtree_shift = "";
else if (!prefixcmp(xopts[x], "subtree="))
o.subtree_shift = xopts[x]+8;
else if (!strcmp(xopts[x], "renormalize"))
o.renormalize = 1;
else if (!strcmp(xopts[x], "no-renormalize"))
o.renormalize = 0;
else
for (x = 0; x < xopts_nr; x++)
if (parse_merge_opt(&o, xopts[x]))
die("Unknown option for merge-recursive: -X%s", xopts[x]);
}

o.branch1 = head_arg;
o.branch2 = remoteheads->item->util;
Expand Down
51 changes: 34 additions & 17 deletions ll-merge.c
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,13 @@ 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;
xmp.xpp.flags = opts->xdl_opts;
if (git_xmerge_style >= 0)
xmp.style = git_xmerge_style;
if (marker_size > 0)
Expand All @@ -96,15 +103,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 +145,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 +348,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 +376,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
22 changes: 7 additions & 15 deletions ll-merge.h
Expand Up @@ -5,27 +5,19 @@
#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;
long xdl_opts;
};

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
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

0 comments on commit 75b17fe

Please sign in to comment.