summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/merge-strategies.txt22
-rw-r--r--Documentation/technical/api-merge.txt71
-rw-r--r--builtin/checkout.c2
-rw-r--r--builtin/merge-recursive.c15
-rw-r--r--builtin/merge.c20
-rw-r--r--ll-merge.c51
-rw-r--r--ll-merge.h22
-rw-r--r--merge-file.c2
-rw-r--r--merge-recursive.c52
-rw-r--r--merge-recursive.h3
-rw-r--r--rerere.c2
-rwxr-xr-xt/t3032-merge-recursive-options.sh186
12 files changed, 351 insertions, 97 deletions
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 8676e26..9cf88e2 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -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
diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt
index a7e050b..9dc1bed 100644
--- a/Documentation/technical/api-merge.txt
+++ b/Documentation/technical/api-merge.txt
@@ -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
-----------------------------
@@ -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
@@ -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
---------------
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a54583b..9240faf 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -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);
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index 78b9db7..c33091b 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -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> ...";
@@ -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;
}
diff --git a/builtin/merge.c b/builtin/merge.c
index 2dba3b9..10f091b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -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;
diff --git a/ll-merge.c b/ll-merge.c
index 6bb3095..007dd3e 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -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 {
@@ -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;
@@ -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) ||
@@ -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)
@@ -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
@@ -136,7 +145,8 @@ 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;
@@ -144,6 +154,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
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];
@@ -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);
@@ -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)
diff --git a/ll-merge.h b/ll-merge.h
index ff7ca87..244a31f 100644
--- a/ll-merge.h
+++ b/ll-merge.h
@@ -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);
diff --git a/merge-file.c b/merge-file.c
index db4d0d5..f7f4533 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -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;
diff --git a/merge-recursive.c b/merge-recursive.c
index c574698..325a97b 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -605,22 +605,26 @@ 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;
+ ll_opts.xdl_opts = o->xdl_opts;
+
+ 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;
}
}
@@ -643,10 +647,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);
@@ -1550,3 +1551,32 @@ void init_merge_options(struct merge_options *o)
memset(&o->current_directory_set, 0, sizeof(struct string_list));
o->current_directory_set.strdup_strings = 1;
}
+
+int parse_merge_opt(struct merge_options *o, const char *s)
+{
+ if (!s || !*s)
+ return -1;
+ if (!strcmp(s, "ours"))
+ o->recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(s, "theirs"))
+ o->recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(s, "subtree"))
+ o->subtree_shift = "";
+ else if (!prefixcmp(s, "subtree="))
+ o->subtree_shift = s + strlen("subtree=");
+ else if (!strcmp(s, "patience"))
+ o->xdl_opts |= XDF_PATIENCE_DIFF;
+ else if (!strcmp(s, "ignore-space-change"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+ else if (!strcmp(s, "ignore-all-space"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE;
+ else if (!strcmp(s, "ignore-space-at-eol"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+ else if (!strcmp(s, "renormalize"))
+ o->renormalize = 1;
+ else if (!strcmp(s, "no-renormalize"))
+ o->renormalize = 0;
+ else
+ return -1;
+ return 0;
+}
diff --git a/merge-recursive.h b/merge-recursive.h
index 34492db..2eb5d1a 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -15,6 +15,7 @@ struct merge_options {
const char *subtree_shift;
unsigned buffer_output : 1;
unsigned renormalize : 1;
+ long xdl_opts;
int verbosity;
int diff_rename_limit;
int merge_rename_limit;
@@ -52,6 +53,8 @@ int merge_recursive_generic(struct merge_options *o,
void init_merge_options(struct merge_options *o);
struct tree *write_tree_from_memory(struct merge_options *o);
+int parse_merge_opt(struct merge_options *out, const char *s);
+
/* builtin/merge.c */
int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
diff --git a/rerere.c b/rerere.c
index 861ca7c..d260843 100644
--- a/rerere.c
+++ b/rerere.c
@@ -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);
diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-options.sh
new file mode 100755
index 0000000..2293797
--- /dev/null
+++ b/t/t3032-merge-recursive-options.sh
@@ -0,0 +1,186 @@
+#!/bin/sh
+
+test_description='merge-recursive options
+
+* [master] Clarify
+ ! [remote] Remove cruft
+--
+ + [remote] Remove cruft
+* [master] Clarify
+*+ [remote^] Initial revision
+* ok 1: setup
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ conflict_hunks () {
+ sed -n -e "
+ /^<<<</ b inconflict
+ b
+ : inconflict
+ p
+ /^>>>>/ b
+ n
+ b inconflict
+ " "$@"
+ } &&
+
+ cat <<-\EOF >text.txt &&
+ Hope, he says, cherishes the soul of him who lives in
+ justice and holiness and is the nurse of his age and the
+ companion of his journey;--hope which is mightiest to sway
+ the restless soul of man.
+
+ How admirable are his words! And the great blessing of riches, I do
+ not say to every man, but to a good man, is, that he has had no
+ occasion to deceive or to defraud others, either intentionally or
+ unintentionally; and when he departs to the world below he is not in
+ any apprehension about offerings due to the gods or debts which he owes
+ to men. Now to this peace of mind the possession of wealth greatly
+ contributes; and therefore I say, that, setting one thing against
+ another, of the many advantages which wealth has to give, to a man of
+ sense this is in my opinion the greatest.
+
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ it?--to speak the truth and to pay your debts--no more than this? And
+ even to this are there not exceptions? Suppose that a friend when in
+ his right mind has deposited arms with me and he asks for them when he
+ is not in his right mind, ought I to give them back to him? No one
+ would say that I ought or that I should be right in doing so, any more
+ than they would say that I ought always to speak the truth to one who
+ is in his condition.
+
+ You are quite right, he replied.
+
+ But then, I said, speaking the truth and paying your debts is not a
+ correct definition of justice.
+
+ CEPHALUS - SOCRATES - POLEMARCHUS
+
+ Quite correct, Socrates, if Simonides is to be believed, said
+ Polemarchus interposing.
+
+ I fear, said Cephalus, that I must go now, for I have to look after the
+ sacrifices, and I hand over the argument to Polemarchus and the company.
+ EOF
+ git add text.txt &&
+ test_tick &&
+ git commit -m "Initial revision" &&
+
+ git checkout -b remote &&
+ sed -e "
+ s/\. /\. /g
+ s/[?] /? /g
+ s/ / /g
+ s/--/---/g
+ s/but as concerning/but as con cerning/
+ /CEPHALUS - SOCRATES - POLEMARCHUS/ d
+ " text.txt >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Remove cruft" &&
+
+ git checkout master &&
+ sed -e "
+ s/\(not in his right mind\),\(.*\)/\1;\2Q/
+ s/Quite correct\(.*\)/It is too correct\1Q/
+ s/unintentionally/un intentionally/
+ /un intentionally/ s/$/Q/
+ s/Polemarchus interposing./Polemarchus, interposing.Q/
+ /justice and holiness/ s/$/Q/
+ /pay your debts/ s/$/Q/
+ " text.txt | q_to_cr >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Clarify" &&
+ git show-branch --all
+'
+
+test_expect_success 'naive merge fails' '
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive HEAD^ -- HEAD remote &&
+ test_must_fail git update-index --refresh &&
+ grep "<<<<<<" text.txt
+'
+
+test_expect_success '--ignore-space-change makes merge succeed' '
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote
+'
+
+test_expect_success '--ignore-space-change: our w/s-only change wins' '
+ q_to_cr <<-\EOF >expected &&
+ justice and holiness and is the nurse of his age and theQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "justice and holiness" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: their real change wins over w/s' '
+ cat <<-\EOF >expected &&
+ it?---to speak the truth and to pay your debts---no more than this? And
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "pay your debts" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: does not ignore new spaces' '
+ cat <<-\EOF >expected1 &&
+ Well said, Cephalus, I replied; but as con cerning justice, what is
+ EOF
+ q_to_cr <<-\EOF >expected2 &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual1 &&
+ grep "when he departs" text.txt >actual2 &&
+ test_cmp expected1 actual1 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success '--ignore-all-space drops their new spaces' '
+ cat <<-\EOF >expected &&
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-all-space keeps our new spaces' '
+ q_to_cr <<-\EOF >expected &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "when he departs" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-at-eol' '
+ q_to_cr <<-\EOF >expected &&
+ <<<<<<< HEAD
+ is not in his right mind; ought I to give them back to him? No oneQ
+ =======
+ is not in his right mind, ought I to give them back to him? No one
+ >>>>>>> remote
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive --ignore-space-at-eol \
+ HEAD^ -- HEAD remote &&
+ conflict_hunks text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_done