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>
This commit is contained in:
Alex Henrie
2024-09-09 08:10:58 -06:00
committed by Junio C Hamano
parent 4c42d5ff28
commit 57f583c748
4 changed files with 67 additions and 3 deletions

View File

@ -9,7 +9,8 @@ git-apply - Apply a patch to files and/or to the index
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way] 'git apply' [--stat] [--numstat] [--summary] [--check]
[--index | --intent-to-add] [--3way] [--ours | --theirs | --union]
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z] [--allow-binary-replacement | --binary] [--reject] [-z]
[-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached] [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
@ -92,6 +93,12 @@ OPTIONS
When used with the `--cached` option, any conflicts are left at higher stages When used with the `--cached` option, any conflicts are left at higher stages
in the cache. in the cache.
--ours::
--theirs::
--union::
Instead of leaving conflicts in the file, resolve conflicts favouring
our (or their or both) side of the lines. Requires --3way.
--build-fake-ancestor=<file>:: --build-fake-ancestor=<file>::
Newer 'git diff' output has embedded 'index information' Newer 'git diff' output has embedded 'index information'
for each blob to help identify the original version that for each blob to help identify the original version that

20
apply.c
View File

@ -3561,6 +3561,7 @@ static int three_way_merge(struct apply_state *state,
const struct object_id *theirs) const struct object_id *theirs)
{ {
mmfile_t base_file, our_file, their_file; mmfile_t base_file, our_file, their_file;
struct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;
mmbuffer_t result = { NULL }; mmbuffer_t result = { NULL };
enum ll_merge_result status; enum ll_merge_result status;
@ -3573,12 +3574,13 @@ static int three_way_merge(struct apply_state *state,
read_mmblob(&base_file, base); read_mmblob(&base_file, base);
read_mmblob(&our_file, ours); read_mmblob(&our_file, ours);
read_mmblob(&their_file, theirs); read_mmblob(&their_file, theirs);
merge_opts.variant = state->merge_variant;
status = ll_merge(&result, path, status = ll_merge(&result, path,
&base_file, "base", &base_file, "base",
&our_file, "ours", &our_file, "ours",
&their_file, "theirs", &their_file, "theirs",
state->repo->index, state->repo->index,
NULL); &merge_opts);
if (status == LL_MERGE_BINARY_CONFLICT) if (status == LL_MERGE_BINARY_CONFLICT)
warning("Cannot merge binary files: %s (%s vs. %s)", warning("Cannot merge binary files: %s (%s vs. %s)",
path, "ours", "theirs"); path, "ours", "theirs");
@ -5151,6 +5153,15 @@ int apply_parse_options(int argc, const char **argv,
N_("also apply the patch (use with --stat/--summary/--check)")), N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_BOOL('3', "3way", &state->threeway, OPT_BOOL('3', "3way", &state->threeway,
N_( "attempt three-way merge, fall back on normal patch if that fails")), N_( "attempt three-way merge, fall back on normal patch if that fails")),
OPT_SET_INT_F(0, "ours", &state->merge_variant,
N_("for conflicts, use our version"),
XDL_MERGE_FAVOR_OURS, PARSE_OPT_NONEG),
OPT_SET_INT_F(0, "theirs", &state->merge_variant,
N_("for conflicts, use their version"),
XDL_MERGE_FAVOR_THEIRS, PARSE_OPT_NONEG),
OPT_SET_INT_F(0, "union", &state->merge_variant,
N_("for conflicts, use a union version"),
XDL_MERGE_FAVOR_UNION, PARSE_OPT_NONEG),
OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor, OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor,
N_("build a temporary index based on embedded index information")), N_("build a temporary index based on embedded index information")),
/* Think twice before adding "--nul" synonym to this */ /* Think twice before adding "--nul" synonym to this */
@ -5190,5 +5201,10 @@ int apply_parse_options(int argc, const char **argv,
OPT_END() OPT_END()
}; };
return parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0); argc = parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);
if (state->merge_variant && !state->threeway)
die(_("--ours, --theirs, and --union require --3way"));
return argc;
} }

View File

@ -59,6 +59,7 @@ struct apply_state {
struct repository *repo; struct repository *repo;
const char *index_file; const char *index_file;
enum apply_verbosity apply_verbosity; enum apply_verbosity apply_verbosity;
int merge_variant;
char *fake_ancestor; char *fake_ancestor;
const char *patch_input_file; const char *patch_input_file;
int line_termination; int line_termination;

View File

@ -82,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '
test_apply_with_3way test_apply_with_3way
' '
test_apply_with_3way_favoritism () {
apply_arg=$1
merge_arg=$2
# Merging side should be similar to applying this patch
git diff ...side >P.diff &&
# The corresponding conflicted merge
git reset --hard &&
git checkout main^0 &&
git merge --no-commit $merge_arg side &&
git ls-files -s >expect.ls &&
print_sanitized_conflicted_diff >expect.diff &&
# should apply successfully
git reset --hard &&
git checkout main^0 &&
git apply --index --3way $apply_arg P.diff &&
git ls-files -s >actual.ls &&
print_sanitized_conflicted_diff >actual.diff &&
# The result should resemble the corresponding merge
test_cmp expect.ls actual.ls &&
test_cmp expect.diff actual.diff
}
test_expect_success 'apply with --3way --ours' '
test_apply_with_3way_favoritism --ours -Xours
'
test_expect_success 'apply with --3way --theirs' '
test_apply_with_3way_favoritism --theirs -Xtheirs
'
test_expect_success 'apply with --3way --union' '
echo "* merge=union" >.gitattributes &&
test_apply_with_3way_favoritism --union &&
rm .gitattributes
'
test_expect_success 'apply with --3way with rerere enabled' ' test_expect_success 'apply with --3way with rerere enabled' '
test_config rerere.enabled true && test_config rerere.enabled true &&