{"thread":{"id":"62071","subject":"[PATCH] apply: support --ours, --theirs, and --union for three-way merges","startedAt":"2024-09-06T04:42:32Z","lastAt":"2024-09-09T14:11:34Z","messageCount":5,"participants":["Alex Henrie","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"502304","messageId":"20240906044222.4881-1-alexhenrie24@gmail.com","threadId":"62071","inReplyTo":null,"subject":"[PATCH] apply: support --ours, --theirs, and --union for three-way merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2024-09-06T04:22:36Z","receivedAt":"2024-09-06T04:42:32Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"--ours, --theirs, and --union are already supported in `git merge-file`\nfor automatically resolving conflicts in favor of one version or the\nother, instead of leaving conflict markers in the file. Support them in\n`git apply -3` as well because the two commands do the same kind of\nfile-level merges.\n\nIn case in the future --ours, --theirs, and --union gain a meaning\noutside of three-way-merges, they do not imply --3way but rather must be\nspecified alongside it.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n Documentation/git-apply.txt |  9 ++++++-\n apply.c                     | 49 ++++++++++++++++++++++++++++++++++---\n apply.h                     |  2 ++\n t/t4108-apply-threeway.sh   | 40 ++++++++++++++++++++++++++++++\n 4 files changed, 96 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt\nindex 9cce68a38b..dd4a61ef28 100644\n--- a/Documentation/git-apply.txt\n+++ b/Documentation/git-apply.txt\n@@ -9,7 +9,8 @@ git-apply - Apply a patch to files and/or to the index\n SYNOPSIS\n --------\n [verse]\n-'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way]\n+'git apply' [--stat] [--numstat] [--summary] [--check]\n+\t  [--index | --intent-to-add] [--3way] [--ours | --theirs | --union]\n \t  [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]\n \t  [--allow-binary-replacement | --binary] [--reject] [-z]\n \t  [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]\n@@ -92,6 +93,12 @@ OPTIONS\n \tWhen used with the `--cached` option, any conflicts are left at higher stages\n \tin the cache.\n \n+--ours::\n+--theirs::\n+--union::\n+\tInstead of leaving conflicts in the file, resolve conflicts favouring\n+\tour (or their or both) side of the lines. Requires --3way.\n+\n --build-fake-ancestor=<file>::\n \tNewer 'git diff' output has embedded 'index information'\n \tfor each blob to help identify the original version that\ndiff --git a/apply.c b/apply.c\nindex 6e1060a952..e47ec6dc56 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -110,6 +110,7 @@ int init_apply_state(struct apply_state *state,\n \tstate->prefix = prefix;\n \tstate->repo = repo;\n \tstate->apply = 1;\n+\tstate->merge_opts.conflict_style = -1;\n \tstate->line_termination = '\\n';\n \tstate->p_value = 1;\n \tstate->p_context = UINT_MAX;\n@@ -3578,7 +3579,7 @@ static int three_way_merge(struct apply_state *state,\n \t\t\t  &our_file, \"ours\",\n \t\t\t  &their_file, \"theirs\",\n \t\t\t  state->repo->index,\n-\t\t\t  NULL);\n+\t\t\t  &state->merge_opts);\n \tif (status == LL_MERGE_BINARY_CONFLICT)\n \t\twarning(\"Cannot merge binary files: %s (%s vs. %s)\",\n \t\t\tpath, \"ours\", \"theirs\");\n@@ -3704,7 +3705,15 @@ static int try_threeway(struct apply_state *state,\n \t\treturn status;\n \t}\n \n-\tif (status) {\n+\tif (state->merge_opts.variant) {\n+\t\t/*\n+\t\t * XDL_MERGE_FAVOR_(OURS|THEIRS|UNION) automatically resolves\n+\t\t * conflicts, but the ll_merge function is not yet smart enough\n+\t\t * to report whether or not there were conflicts, so just print\n+\t\t * a generic message.\n+\t\t */\n+\t\tfprintf(stderr, _(\"Applied patch to '%s'.\\n\"), patch->new_name);\n+\t} else if (status) {\n \t\tpatch->conflicted_threeway = 1;\n \t\tif (patch->is_new)\n \t\t\toidclr(&patch->threeway_stage[0], the_repository->hash_algo);\n@@ -4980,6 +4989,23 @@ static int apply_option_parse_space_change(const struct option *opt,\n \treturn 0;\n }\n \n+static int apply_option_parse_favorite(const struct option *opt,\n+\t\t\t\t       const char *arg, int unset)\n+{\n+\tstruct apply_state *state = opt->value;\n+\n+\tBUG_ON_OPT_ARG(arg);\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(opt->long_name, \"ours\"))\n+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_OURS;\n+\telse if (!strcmp(opt->long_name, \"theirs\"))\n+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n+\telse\n+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_UNION;\n+\treturn 0;\n+}\n+\n static int apply_option_parse_whitespace(const struct option *opt,\n \t\t\t\t\t const char *arg, int unset)\n {\n@@ -5151,6 +5177,18 @@ int apply_parse_options(int argc, const char **argv,\n \t\t\tN_(\"also apply the patch (use with --stat/--summary/--check)\")),\n \t\tOPT_BOOL('3', \"3way\", &state->threeway,\n \t\t\t N_( \"attempt three-way merge, fall back on normal patch if that fails\")),\n+\t\tOPT_CALLBACK_F(0, \"ours\", state, NULL,\n+\t\t\tN_(\"for conflicts, use our version\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tapply_option_parse_favorite),\n+\t\tOPT_CALLBACK_F(0, \"theirs\", state, NULL,\n+\t\t\tN_(\"for conflicts, use their version\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tapply_option_parse_favorite),\n+\t\tOPT_CALLBACK_F(0, \"union\", state, NULL,\n+\t\t\tN_(\"for conflicts, use a union version\"),\n+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n+\t\t\tapply_option_parse_favorite),\n \t\tOPT_FILENAME(0, \"build-fake-ancestor\", &state->fake_ancestor,\n \t\t\tN_(\"build a temporary index based on embedded index information\")),\n \t\t/* Think twice before adding \"--nul\" synonym to this */\n@@ -5190,5 +5228,10 @@ int apply_parse_options(int argc, const char **argv,\n \t\tOPT_END()\n \t};\n \n-\treturn parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n+\targc = parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n+\n+\tif (state->merge_opts.variant && !state->threeway)\n+\t\tdie(_(\"--ours, --theirs, and --union require --3way\"));\n+\n+\treturn argc;\n }\ndiff --git a/apply.h b/apply.h\nindex cd25d24cc4..096f4472b4 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -3,6 +3,7 @@\n \n #include \"hash.h\"\n #include \"lockfile.h\"\n+#include \"merge-ll.h\"\n #include \"string-list.h\"\n #include \"strmap.h\"\n \n@@ -59,6 +60,7 @@ struct apply_state {\n \tstruct repository *repo;\n \tconst char *index_file;\n \tenum apply_verbosity apply_verbosity;\n+\tstruct ll_merge_options merge_opts;\n \tchar *fake_ancestor;\n \tconst char *patch_input_file;\n \tint line_termination;\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex 3211e1e65f..c6302163d8 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -82,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '\n \ttest_apply_with_3way\n '\n \n+test_apply_with_3way_favoritism () {\n+\tapply_arg=$1\n+\tmerge_arg=$2\n+\n+\t# Merging side should be similar to applying this patch\n+\tgit diff ...side >P.diff &&\n+\n+\t# The corresponding conflicted merge\n+\tgit reset --hard &&\n+\tgit checkout main^0 &&\n+\tgit merge --no-commit $merge_arg side &&\n+\tgit ls-files -s >expect.ls &&\n+\tprint_sanitized_conflicted_diff >expect.diff &&\n+\n+\t# should apply successfully\n+\tgit reset --hard &&\n+\tgit checkout main^0 &&\n+\tgit apply --index --3way $apply_arg P.diff &&\n+\tgit ls-files -s >actual.ls &&\n+\tprint_sanitized_conflicted_diff >actual.diff &&\n+\n+\t# The result should resemble the corresponding merge\n+\ttest_cmp expect.ls actual.ls &&\n+\ttest_cmp expect.diff actual.diff\n+}\n+\n+test_expect_success 'apply with --3way --ours' '\n+\ttest_apply_with_3way_favoritism --ours -Xours\n+'\n+\n+test_expect_success 'apply with --3way --theirs' '\n+\ttest_apply_with_3way_favoritism --theirs -Xtheirs\n+'\n+\n+test_expect_success 'apply with --3way --union' '\n+\techo \"* merge=union\" >.gitattributes &&\n+\ttest_apply_with_3way_favoritism --union &&\n+\trm .gitattributes\n+'\n+\n test_expect_success 'apply with --3way with rerere enabled' '\n \ttest_config rerere.enabled true &&\n \n-- \n2.46.0\n\n"},{"id":"502360","messageId":"xmqqwmjov772.fsf@gitster.g","threadId":"62071","inReplyTo":"20240906044222.4881-1-alexhenrie24@gmail.com","subject":"Re: [PATCH] apply: support --ours, --theirs, and --union for three-way merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-06T20:49:21Z","receivedAt":"2024-09-06T20:49:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alex Henrie <alexhenrie24@gmail.com> writes:\n\n> --ours, --theirs, and --union are already supported in `git merge-file`\n> for automatically resolving conflicts in favor of one version or the\n> other, instead of leaving conflict markers in the file. Support them in\n> `git apply -3` as well because the two commands do the same kind of\n> file-level merges.\n\nAn unrelated #leftoverbits tangent.  \n\nWe probably should teach add \"union\" as a valid choice in\n.recursive_variant in merge-recursive.c:parse_merge_opt(), together\nwith \"ours\" and \"theirs\" that are already supported.\n\n> In case in the future --ours, --theirs, and --union gain a meaning\n> outside of three-way-merges, they do not imply --3way but rather must be\n> specified alongside it.\n\nOK.  At least the code insists on having --3way specified, instead\nof silently ignoring --ours given without --3way, so it is good.\n\n> +static int apply_option_parse_favorite(const struct option *opt,\n> +\t\t\t\t       const char *arg, int unset)\n> +{\n> +\tstruct apply_state *state = opt->value;\n> +\n> +\tBUG_ON_OPT_ARG(arg);\n> +\tBUG_ON_OPT_NEG(unset);\n> +\n> +\tif (!strcmp(opt->long_name, \"ours\"))\n> +\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_OURS;\n> +\telse if (!strcmp(opt->long_name, \"theirs\"))\n> +\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n> +\telse\n> +\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_UNION;\n> +\treturn 0;\n> +}\n\nIf you MUST use a opt-callback, then do not assume anything that is\nnot ours or theirs will always be union.  Help future developers by\nmaking your assumption more explicit, i.e.\n\n\tif (...\"ours\"...)\n\t\tdo ours thing;\n\telse if (... \"theirs\" ...)\n\t\tdo theirs thing;\n\telse if (... \"union\" ...)\n\t\tdo union thing;\n\telse\n\t\tBUG(\"unexpected option '--%s'\", opt->long_name);\n\nHaving said that, I do not think you want or need a callback in this\ncase to begin with.\n\n>  static int apply_option_parse_whitespace(const struct option *opt,\n>  \t\t\t\t\t const char *arg, int unset)\n>  {\n> @@ -5151,6 +5177,18 @@ int apply_parse_options(int argc, const char **argv,\n>  \t\t\tN_(\"also apply the patch (use with --stat/--summary/--check)\")),\n>  \t\tOPT_BOOL('3', \"3way\", &state->threeway,\n>  \t\t\t N_( \"attempt three-way merge, fall back on normal patch if that fails\")),\n> +\t\tOPT_CALLBACK_F(0, \"ours\", state, NULL,\n> +\t\t\tN_(\"for conflicts, use our version\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n> +\t\t\tapply_option_parse_favorite),\n> +\t\tOPT_CALLBACK_F(0, \"theirs\", state, NULL,\n> +\t\t\tN_(\"for conflicts, use their version\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n> +\t\t\tapply_option_parse_favorite),\n> +\t\tOPT_CALLBACK_F(0, \"union\", state, NULL,\n> +\t\t\tN_(\"for conflicts, use a union version\"),\n> +\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n> +\t\t\tapply_option_parse_favorite),\n\nInstead of embedding the whole ll_merge_options in apply_state, just\ndefine a new integer member \"merge_variant\", and use OPT_SET_INT()\non that field.  That way, you won't have to do the callback interface\nand worry about keeping the function's if/elseif cascade in sync\nwith these options that call the same function.\n\nThen, instead of passing state->merge_opts, you can initialize\nll_merge_options instance in three_way_merge() with whatever is\nneeded from the apply_state (like the merge_variant mentioned\nabove).\n\n> -\tif (status) {\n> +\tif (state->merge_opts.variant) {\n> +\t\t/*\n> +\t\t * XDL_MERGE_FAVOR_(OURS|THEIRS|UNION) automatically resolves\n> +\t\t * conflicts, but the ll_merge function is not yet smart enough\n> +\t\t * to report whether or not there were conflicts, so just print\n> +\t\t * a generic message.\n> +\t\t */\n> +\t\tfprintf(stderr, _(\"Applied patch to '%s'.\\n\"), patch->new_name);\n\nI do not think this extra message or the comment is warranted.  When\nyou said \"--ours\" you told the machinery that you favor our version,\nso there is no place to be \"smart enough to report\".  Instead of\nnormal 3-way merge, you told it there won't be any conflict.  There\nis nothing to report.\n\nThe else clause of the original \"if (status)\" does report \"applied\npatch cleanly\" when apply_verbosity is set to report it.  As far as\nthe command is concerned, if you told it to use \"ours\" and got a\nmerge result, that was also applied cleanly.  So you can just drop\nthis hunk and let the existing code take care of the rest (including\nhonoring the verbosity settings).\n\nOther than that, looks fairly straight-forward.\n\nI didn't read the tests, though.\n\nThanks.\n"},{"id":"502448","messageId":"CAMMLpeREE8VCUzdta6oFrHqT=ctOPYxKBBJ1=6j0QpbAMgrhGA@mail.gmail.com","threadId":"62071","inReplyTo":"xmqqwmjov772.fsf@gitster.g","subject":"Re: [PATCH] apply: support --ours, --theirs, and --union for three-way merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2024-09-09T14:10:06Z","receivedAt":"2024-09-09T14:10:06Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"On Fri, Sep 6, 2024 at 2:49 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n> An unrelated #leftoverbits tangent.\n>\n> We probably should teach add \"union\" as a valid choice in\n> .recursive_variant in merge-recursive.c:parse_merge_opt(), together\n> with \"ours\" and \"theirs\" that are already supported.\n\nI agree, that would be really nice, and it would simplify the tests\nfor `git apply -3 --union`.\n\nThe other changes you requested are done in v2. Thanks for the feedback!\n\n-Alex\n"},{"id":"502449","messageId":"20240909141109.3102-1-alexhenrie24@gmail.com","threadId":"62071","inReplyTo":"20240906044222.4881-1-alexhenrie24@gmail.com","subject":"[PATCH v2 0/1] apply: support --ours, --theirs, and --union for three-way merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2024-09-09T14:10:57Z","receivedAt":"2024-09-09T14:11:33Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"Changes from v1:\n- Use OPT_SET_INT_F instead of OPT_CALLBACK_F\n- Remove the special message and instead always say that the patch\n  applied cleanly when these options are used\n\nThanks to Junio for your feedback.\n\nAlex Henrie (1):\n  apply: support --ours, --theirs, and --union for three-way merges\n\n Documentation/git-apply.txt |  9 ++++++++-\n apply.c                     | 20 +++++++++++++++++--\n apply.h                     |  1 +\n t/t4108-apply-threeway.sh   | 40 +++++++++++++++++++++++++++++++++++++\n 4 files changed, 67 insertions(+), 3 deletions(-)\n\nRange-diff against v1:\n1:  e0e4000d47 ! 1:  9307dc5a1b apply: support --ours, --theirs, and --union for three-way merges\n    @@ Documentation/git-apply.txt: OPTIONS\n      \tfor each blob to help identify the original version that\n     \n      ## apply.c ##\n    -@@ apply.c: int init_apply_state(struct apply_state *state,\n    - \tstate->prefix = prefix;\n    - \tstate->repo = repo;\n    - \tstate->apply = 1;\n    -+\tstate->merge_opts.conflict_style = -1;\n    - \tstate->line_termination = '\\n';\n    - \tstate->p_value = 1;\n    - \tstate->p_context = UINT_MAX;\n     @@ apply.c: static int three_way_merge(struct apply_state *state,\n    + \t\t\t   const struct object_id *theirs)\n    + {\n    + \tmmfile_t base_file, our_file, their_file;\n    ++\tstruct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;\n    + \tmmbuffer_t result = { NULL };\n    + \tenum ll_merge_result status;\n    + \n    +@@ apply.c: static int three_way_merge(struct apply_state *state,\n    + \tread_mmblob(&base_file, base);\n    + \tread_mmblob(&our_file, ours);\n    + \tread_mmblob(&their_file, theirs);\n    ++\tmerge_opts.variant = state->merge_variant;\n    + \tstatus = ll_merge(&result, path,\n    + \t\t\t  &base_file, \"base\",\n      \t\t\t  &our_file, \"ours\",\n      \t\t\t  &their_file, \"theirs\",\n      \t\t\t  state->repo->index,\n     -\t\t\t  NULL);\n    -+\t\t\t  &state->merge_opts);\n    ++\t\t\t  &merge_opts);\n      \tif (status == LL_MERGE_BINARY_CONFLICT)\n      \t\twarning(\"Cannot merge binary files: %s (%s vs. %s)\",\n      \t\t\tpath, \"ours\", \"theirs\");\n    -@@ apply.c: static int try_threeway(struct apply_state *state,\n    - \t\treturn status;\n    - \t}\n    - \n    --\tif (status) {\n    -+\tif (state->merge_opts.variant) {\n    -+\t\t/*\n    -+\t\t * XDL_MERGE_FAVOR_(OURS|THEIRS|UNION) automatically resolves\n    -+\t\t * conflicts, but the ll_merge function is not yet smart enough\n    -+\t\t * to report whether or not there were conflicts, so just print\n    -+\t\t * a generic message.\n    -+\t\t */\n    -+\t\tfprintf(stderr, _(\"Applied patch to '%s'.\\n\"), patch->new_name);\n    -+\t} else if (status) {\n    - \t\tpatch->conflicted_threeway = 1;\n    - \t\tif (patch->is_new)\n    - \t\t\toidclr(&patch->threeway_stage[0], the_repository->hash_algo);\n    -@@ apply.c: static int apply_option_parse_space_change(const struct option *opt,\n    - \treturn 0;\n    - }\n    - \n    -+static int apply_option_parse_favorite(const struct option *opt,\n    -+\t\t\t\t       const char *arg, int unset)\n    -+{\n    -+\tstruct apply_state *state = opt->value;\n    -+\n    -+\tBUG_ON_OPT_ARG(arg);\n    -+\tBUG_ON_OPT_NEG(unset);\n    -+\n    -+\tif (!strcmp(opt->long_name, \"ours\"))\n    -+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_OURS;\n    -+\telse if (!strcmp(opt->long_name, \"theirs\"))\n    -+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n    -+\telse\n    -+\t\tstate->merge_opts.variant = XDL_MERGE_FAVOR_UNION;\n    -+\treturn 0;\n    -+}\n    -+\n    - static int apply_option_parse_whitespace(const struct option *opt,\n    - \t\t\t\t\t const char *arg, int unset)\n    - {\n     @@ apply.c: int apply_parse_options(int argc, const char **argv,\n      \t\t\tN_(\"also apply the patch (use with --stat/--summary/--check)\")),\n      \t\tOPT_BOOL('3', \"3way\", &state->threeway,\n      \t\t\t N_( \"attempt three-way merge, fall back on normal patch if that fails\")),\n    -+\t\tOPT_CALLBACK_F(0, \"ours\", state, NULL,\n    ++\t\tOPT_SET_INT_F(0, \"ours\", &state->merge_variant,\n     +\t\t\tN_(\"for conflicts, use our version\"),\n    -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n    -+\t\t\tapply_option_parse_favorite),\n    -+\t\tOPT_CALLBACK_F(0, \"theirs\", state, NULL,\n    ++\t\t\tXDL_MERGE_FAVOR_OURS, PARSE_OPT_NONEG),\n    ++\t\tOPT_SET_INT_F(0, \"theirs\", &state->merge_variant,\n     +\t\t\tN_(\"for conflicts, use their version\"),\n    -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n    -+\t\t\tapply_option_parse_favorite),\n    -+\t\tOPT_CALLBACK_F(0, \"union\", state, NULL,\n    ++\t\t\tXDL_MERGE_FAVOR_THEIRS, PARSE_OPT_NONEG),\n    ++\t\tOPT_SET_INT_F(0, \"union\", &state->merge_variant,\n     +\t\t\tN_(\"for conflicts, use a union version\"),\n    -+\t\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG,\n    -+\t\t\tapply_option_parse_favorite),\n    ++\t\t\tXDL_MERGE_FAVOR_UNION, PARSE_OPT_NONEG),\n      \t\tOPT_FILENAME(0, \"build-fake-ancestor\", &state->fake_ancestor,\n      \t\t\tN_(\"build a temporary index based on embedded index information\")),\n      \t\t/* Think twice before adding \"--nul\" synonym to this */\n    @@ apply.c: int apply_parse_options(int argc, const char **argv,\n     -\treturn parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n     +\targc = parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n     +\n    -+\tif (state->merge_opts.variant && !state->threeway)\n    ++\tif (state->merge_variant && !state->threeway)\n     +\t\tdie(_(\"--ours, --theirs, and --union require --3way\"));\n     +\n     +\treturn argc;\n      }\n     \n      ## apply.h ##\n    -@@\n    - \n    - #include \"hash.h\"\n    - #include \"lockfile.h\"\n    -+#include \"merge-ll.h\"\n    - #include \"string-list.h\"\n    - #include \"strmap.h\"\n    - \n     @@ apply.h: struct apply_state {\n      \tstruct repository *repo;\n      \tconst char *index_file;\n      \tenum apply_verbosity apply_verbosity;\n    -+\tstruct ll_merge_options merge_opts;\n    ++\tint merge_variant;\n      \tchar *fake_ancestor;\n      \tconst char *patch_input_file;\n      \tint line_termination;\n-- \n2.46.0\n\n"},{"id":"502450","messageId":"20240909141109.3102-2-alexhenrie24@gmail.com","threadId":"62071","inReplyTo":"20240909141109.3102-1-alexhenrie24@gmail.com","subject":"[PATCH v2 1/1] apply: support --ours, --theirs, and --union for three-way merges","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2024-09-09T14:10:58Z","receivedAt":"2024-09-09T14:11:34Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"--ours, --theirs, and --union are already supported in `git merge-file`\nfor automatically resolving conflicts in favor of one version or the\nother, instead of leaving conflict markers in the file. Support them in\n`git apply -3` as well because the two commands do the same kind of\nfile-level merges.\n\nIn case in the future --ours, --theirs, and --union gain a meaning\noutside of three-way-merges, they do not imply --3way but rather must be\nspecified alongside it.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\n Documentation/git-apply.txt |  9 ++++++++-\n apply.c                     | 20 +++++++++++++++++--\n apply.h                     |  1 +\n t/t4108-apply-threeway.sh   | 40 +++++++++++++++++++++++++++++++++++++\n 4 files changed, 67 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt\nindex 9cce68a38b..dd4a61ef28 100644\n--- a/Documentation/git-apply.txt\n+++ b/Documentation/git-apply.txt\n@@ -9,7 +9,8 @@ git-apply - Apply a patch to files and/or to the index\n SYNOPSIS\n --------\n [verse]\n-'git apply' [--stat] [--numstat] [--summary] [--check] [--index | --intent-to-add] [--3way]\n+'git apply' [--stat] [--numstat] [--summary] [--check]\n+\t  [--index | --intent-to-add] [--3way] [--ours | --theirs | --union]\n \t  [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]\n \t  [--allow-binary-replacement | --binary] [--reject] [-z]\n \t  [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]\n@@ -92,6 +93,12 @@ OPTIONS\n \tWhen used with the `--cached` option, any conflicts are left at higher stages\n \tin the cache.\n \n+--ours::\n+--theirs::\n+--union::\n+\tInstead of leaving conflicts in the file, resolve conflicts favouring\n+\tour (or their or both) side of the lines. Requires --3way.\n+\n --build-fake-ancestor=<file>::\n \tNewer 'git diff' output has embedded 'index information'\n \tfor each blob to help identify the original version that\ndiff --git a/apply.c b/apply.c\nindex 6e1060a952..cc885f8fec 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3561,6 +3561,7 @@ static int three_way_merge(struct apply_state *state,\n \t\t\t   const struct object_id *theirs)\n {\n \tmmfile_t base_file, our_file, their_file;\n+\tstruct ll_merge_options merge_opts = LL_MERGE_OPTIONS_INIT;\n \tmmbuffer_t result = { NULL };\n \tenum ll_merge_result status;\n \n@@ -3573,12 +3574,13 @@ static int three_way_merge(struct apply_state *state,\n \tread_mmblob(&base_file, base);\n \tread_mmblob(&our_file, ours);\n \tread_mmblob(&their_file, theirs);\n+\tmerge_opts.variant = state->merge_variant;\n \tstatus = ll_merge(&result, path,\n \t\t\t  &base_file, \"base\",\n \t\t\t  &our_file, \"ours\",\n \t\t\t  &their_file, \"theirs\",\n \t\t\t  state->repo->index,\n-\t\t\t  NULL);\n+\t\t\t  &merge_opts);\n \tif (status == LL_MERGE_BINARY_CONFLICT)\n \t\twarning(\"Cannot merge binary files: %s (%s vs. %s)\",\n \t\t\tpath, \"ours\", \"theirs\");\n@@ -5151,6 +5153,15 @@ int apply_parse_options(int argc, const char **argv,\n \t\t\tN_(\"also apply the patch (use with --stat/--summary/--check)\")),\n \t\tOPT_BOOL('3', \"3way\", &state->threeway,\n \t\t\t N_( \"attempt three-way merge, fall back on normal patch if that fails\")),\n+\t\tOPT_SET_INT_F(0, \"ours\", &state->merge_variant,\n+\t\t\tN_(\"for conflicts, use our version\"),\n+\t\t\tXDL_MERGE_FAVOR_OURS, PARSE_OPT_NONEG),\n+\t\tOPT_SET_INT_F(0, \"theirs\", &state->merge_variant,\n+\t\t\tN_(\"for conflicts, use their version\"),\n+\t\t\tXDL_MERGE_FAVOR_THEIRS, PARSE_OPT_NONEG),\n+\t\tOPT_SET_INT_F(0, \"union\", &state->merge_variant,\n+\t\t\tN_(\"for conflicts, use a union version\"),\n+\t\t\tXDL_MERGE_FAVOR_UNION, PARSE_OPT_NONEG),\n \t\tOPT_FILENAME(0, \"build-fake-ancestor\", &state->fake_ancestor,\n \t\t\tN_(\"build a temporary index based on embedded index information\")),\n \t\t/* Think twice before adding \"--nul\" synonym to this */\n@@ -5190,5 +5201,10 @@ int apply_parse_options(int argc, const char **argv,\n \t\tOPT_END()\n \t};\n \n-\treturn parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n+\targc = parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0);\n+\n+\tif (state->merge_variant && !state->threeway)\n+\t\tdie(_(\"--ours, --theirs, and --union require --3way\"));\n+\n+\treturn argc;\n }\ndiff --git a/apply.h b/apply.h\nindex cd25d24cc4..90e887ec0e 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -59,6 +59,7 @@ struct apply_state {\n \tstruct repository *repo;\n \tconst char *index_file;\n \tenum apply_verbosity apply_verbosity;\n+\tint merge_variant;\n \tchar *fake_ancestor;\n \tconst char *patch_input_file;\n \tint line_termination;\ndiff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh\nindex 3211e1e65f..c6302163d8 100755\n--- a/t/t4108-apply-threeway.sh\n+++ b/t/t4108-apply-threeway.sh\n@@ -82,6 +82,46 @@ test_expect_success 'apply with --3way with merge.conflictStyle = diff3' '\n \ttest_apply_with_3way\n '\n \n+test_apply_with_3way_favoritism () {\n+\tapply_arg=$1\n+\tmerge_arg=$2\n+\n+\t# Merging side should be similar to applying this patch\n+\tgit diff ...side >P.diff &&\n+\n+\t# The corresponding conflicted merge\n+\tgit reset --hard &&\n+\tgit checkout main^0 &&\n+\tgit merge --no-commit $merge_arg side &&\n+\tgit ls-files -s >expect.ls &&\n+\tprint_sanitized_conflicted_diff >expect.diff &&\n+\n+\t# should apply successfully\n+\tgit reset --hard &&\n+\tgit checkout main^0 &&\n+\tgit apply --index --3way $apply_arg P.diff &&\n+\tgit ls-files -s >actual.ls &&\n+\tprint_sanitized_conflicted_diff >actual.diff &&\n+\n+\t# The result should resemble the corresponding merge\n+\ttest_cmp expect.ls actual.ls &&\n+\ttest_cmp expect.diff actual.diff\n+}\n+\n+test_expect_success 'apply with --3way --ours' '\n+\ttest_apply_with_3way_favoritism --ours -Xours\n+'\n+\n+test_expect_success 'apply with --3way --theirs' '\n+\ttest_apply_with_3way_favoritism --theirs -Xtheirs\n+'\n+\n+test_expect_success 'apply with --3way --union' '\n+\techo \"* merge=union\" >.gitattributes &&\n+\ttest_apply_with_3way_favoritism --union &&\n+\trm .gitattributes\n+'\n+\n test_expect_success 'apply with --3way with rerere enabled' '\n \ttest_config rerere.enabled true &&\n \n-- \n2.46.0\n\n"}]}