apply: support --ours, --theirs, and --union for three-way merges
--ours, --theirs, and --union are already supported in `git merge-file` for automatically resolving conflicts in favor of one version or the other, instead of leaving conflict markers in the file. Support them in `git apply -3` as well because the two commands do the same kind of file-level merges. In case in the future --ours, --theirs, and --union gain a meaning outside of three-way-merges, they do not imply --3way but rather must be specified alongside it. Signed-off-by: Alex Henrie <alexhenrie24@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Alex Henrie committed
Sep 9, 2024 at 08:10 UTC
57f583c748fdc65c16d1c24a7060a758850cb4a2
4 files changed
+67
-3
Documentation/git-apply.txt
+8
-1
@@ -9,7 +9,8 @@ git-apply - Apply a patch to files and/or to the index
9
SYNOPSIS
10
--------
11
[verse]
12
-'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way]
12
+'git apply' [--stat] [--numstat] [--summary] [--check]
13
+ [--index | --intent-to-add] [--3way] [--ours | --theirs | --union]
14
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
15
[--allow-binary-replacement | --binary] [--reject] [-z]
16
[-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
@@ -92,6 +93,12 @@ OPTIONS
93
When used with the `--cached` option, any conflicts are left at higher stages
94
in the cache.
95
96
+--ours::
97
+--theirs::
98
+--union::
99
+ Instead of leaving conflicts in the file, resolve conflicts favouring
100
+ our (or their or both) side of the lines. Requires --3way.
101
+
102
--build-fake-ancestor=<file>::
103
Newer 'git diff' output has embedded 'index information'
104
for each blob to help identify the original version that
apply.c
+18
-2
@@ -3561,6 +3561,7 @@ static int three_way_merge(struct apply_state *state,
3561
const struct object_id *theirs)
3562
{
3563
mmfile_t base_file, our_file, their_file;
3564
+ struct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;
3565
mmbuffer_t result = { NULL };
3566
enum ll_merge_result status;
3567
@@ -3573,12 +3574,13 @@ static int three_way_merge(struct apply_state *state,
3574
read_mmblob(&base_file, base);
3575
read_mmblob(&our_file, ours);
3576
read_mmblob(&their_file, theirs);
3577
+ merge_opts.variant = state->merge_variant;
3578
status = ll_merge(&result, path,
3579
&base_file, "base",
3580
&our_file, "ours",
3581
&their_file, "theirs",
3582
state->repo->index,
3581
- NULL);
3583
+ &merge_opts);
3584
if (status == LL_MERGE_BINARY_CONFLICT)
3585
warning("Cannot merge binary files: %s (%s vs. %s)",
3586
path, "ours", "theirs");
@@ -5151,6 +5153,15 @@ int apply_parse_options(int argc, const char **argv,
5153
N_("also apply the patch (use with --stat/--summary/--check)")),
5154
OPT_BOOL('3', "3way", &state->threeway,
5155
N_( "attempt three-way merge, fall back on normal patch if that fails")),
5156
+ OPT_SET_INT_F(0, "ours", &state->merge_variant,
5157
+ N_("for conflicts, use our version"),
5158
+ XDL_MERGE_FAVOR_OURS, PARSE_OPT_NONEG),
5159
+ OPT_SET_INT_F(0, "theirs", &state->merge_variant,
5160
+ N_("for conflicts, use their version"),
5161
+ XDL_MERGE_FAVOR_THEIRS, PARSE_OPT_NONEG),
5162
+ OPT_SET_INT_F(0, "union", &state->merge_variant,
5163
+ N_("for conflicts, use a union version"),
5164
+ XDL_MERGE_FAVOR_UNION, PARSE_OPT_NONEG),
5165
OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor,
5166
N_("build a temporary index based on embedded index information")),
5167
/* Think twice before adding "--nul" synonym to this */
@@ -5190,5 +5201,10 @@ int apply_parse_options(int argc, const char **argv,
5201
OPT_END()
5202
};
5203
5193
- return parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);
5204
+ argc = parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);
5205
+
5206
+ if (state->merge_variant && !state->threeway)
5207
+ die(_("--ours, --theirs, and --union require --3way"));
5208
+
5209
+ return argc;
5210
}
apply.h
+1
@@ -59,6 +59,7 @@ struct apply_state {
59
struct repository *repo;
60
const char *index_file;
61
enum apply_verbosity apply_verbosity;
62
+ int merge_variant;
63
char *fake_ancestor;
64
const char *patch_input_file;
65
int line_termination;
t/t4108-apply-threeway.sh
+40
@@ -82,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '
82
test_apply_with_3way
83
'
84
85
+test_apply_with_3way_favoritism () {
86
+ apply_arg=$1
87
+ merge_arg=$2
88
+
89
+ # Merging side should be similar to applying this patch
90
+ git diff ...side >P.diff &&
91
+
92
+ # The corresponding conflicted merge
93
+ git reset --hard &&
94
+ git checkout main^0 &&
95
+ git merge --no-commit $merge_arg side &&
96
+ git ls-files -s >expect.ls &&
97
+ print_sanitized_conflicted_diff >expect.diff &&
98
+
99
+ # should apply successfully
100
+ git reset --hard &&
101
+ git checkout main^0 &&
102
+ git apply --index --3way $apply_arg P.diff &&
103
+ git ls-files -s >actual.ls &&
104
+ print_sanitized_conflicted_diff >actual.diff &&
105
+
106
+ # The result should resemble the corresponding merge
107
+ test_cmp expect.ls actual.ls &&
108
+ test_cmp expect.diff actual.diff
109
+}
110
+
111
+test_expect_success 'apply with --3way --ours' '
112
+ test_apply_with_3way_favoritism --ours -Xours
113
+'
114
+
115
+test_expect_success 'apply with --3way --theirs' '
116
+ test_apply_with_3way_favoritism --theirs -Xtheirs
117
+'
118
+
119
+test_expect_success 'apply with --3way --union' '
120
+ echo "* merge=union" >.gitattributes &&
121
+ test_apply_with_3way_favoritism --union &&
122
+ rm .gitattributes
123
+'
124
+
125
test_expect_success 'apply with --3way with rerere enabled' '
126
test_config rerere.enabled true &&
127