]> git.ipfire.org Git - thirdparty/git.git/commitdiff
apply: support --ours, --theirs, and --union for three-way merges
authorAlex Henrie <alexhenrie24@gmail.com>
Mon, 9 Sep 2024 14:10:58 +0000 (08:10 -0600)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2024 17:07:24 +0000 (10:07 -0700)
--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>
Documentation/git-apply.txt
apply.c
apply.h
t/t4108-apply-threeway.sh

index 9cce68a38be10f3a4ff3130adb1a2b300835cdcb..dd4a61ef28933939c2b7fe8efbdda975861446a1 100644 (file)
@@ -9,7 +9,8 @@ git-apply - Apply a patch to files and/or to the index
 SYNOPSIS
 --------
 [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]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-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
        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>::
        Newer 'git diff' output has embedded 'index information'
        for each blob to help identify the original version that
diff --git a/apply.c b/apply.c
index 6e1060a952c31769eb7294d8b93e225f53278c78..cc885f8fecadf0737cd8845e524a9294d38082eb 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -3561,6 +3561,7 @@ static int three_way_merge(struct apply_state *state,
                           const struct object_id *theirs)
 {
        mmfile_t base_file, our_file, their_file;
+       struct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;
        mmbuffer_t result = { NULL };
        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(&our_file, ours);
        read_mmblob(&their_file, theirs);
+       merge_opts.variant = state->merge_variant;
        status = ll_merge(&result, path,
                          &base_file, "base",
                          &our_file, "ours",
                          &their_file, "theirs",
                          state->repo->index,
-                         NULL);
+                         &merge_opts);
        if (status == LL_MERGE_BINARY_CONFLICT)
                warning("Cannot merge binary files: %s (%s vs. %s)",
                        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)")),
                OPT_BOOL('3', "3way", &state->threeway,
                         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,
                        N_("build a temporary index based on embedded index information")),
                /* Think twice before adding "--nul" synonym to this */
@@ -5190,5 +5201,10 @@ int apply_parse_options(int argc, const char **argv,
                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;
 }
diff --git a/apply.h b/apply.h
index cd25d24cc460de45b56dceafa8ca3ef165ada83d..90e887ec0edadf68acf5dd534b7c3e9aa9a917c2 100644 (file)
--- a/apply.h
+++ b/apply.h
@@ -59,6 +59,7 @@ struct apply_state {
        struct repository *repo;
        const char *index_file;
        enum apply_verbosity apply_verbosity;
+       int merge_variant;
        char *fake_ancestor;
        const char *patch_input_file;
        int line_termination;
index 3211e1e65fcf55b7abdd2c254d3ef528d28cd25a..c6302163d848a7add7e1f8f1d3d060bce515f738 100755 (executable)
@@ -82,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '
        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_config rerere.enabled true &&