Skip to content

Commit 923cd87

Browse files
jerry-skydiogitster
authored andcommitted
git-apply: try threeway first when "--3way" is used
The apply_fragments() method of "git apply" can silently apply patches incorrectly if a file has repeating contents. In these cases a three-way merge is capable of applying it correctly in more situations, and will show a conflict rather than applying it incorrectly. However, because the patches apply "successfully" using apply_fragments(), git will never fall back to the merge, even if the "--3way" flag is used, and the user has no way to ensure correctness by forcing the three-way merge method. Change the behavior so that when "--3way" is used, git will always try the three-way merge first and will only fall back to apply_fragments() in cases where blobs are not available or some other error (but not in the case of a merge conflict). Since user-facing results will be different, this has backwards compatibility implications for users depending on the old behavior. In addition, the three-way merge will be slower than direct patch application. Signed-off-by: Jerry Zhang <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2e36527 commit 923cd87

File tree

3 files changed

+28
-10
lines changed

3 files changed

+28
-10
lines changed

Documentation/git-apply.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,8 @@ OPTIONS
8484

8585
-3::
8686
--3way::
87-
When the patch does not apply cleanly, fall back on 3-way merge if
88-
the patch records the identity of blobs it is supposed to apply to,
89-
and we have those blobs available locally, possibly leaving the
87+
Attempt 3-way merge if the patch records the identity of blobs it is supposed
88+
to apply to and we have those blobs available locally, possibly leaving the
9089
conflict markers in the files in the working tree for the user to
9190
resolve. This option implies the `--index` option, and is incompatible
9291
with the `--reject` and the `--cached` options.

apply.c

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3570,10 +3570,10 @@ static int try_threeway(struct apply_state *state,
35703570
write_object_file("", 0, blob_type, &pre_oid);
35713571
else if (get_oid(patch->old_oid_prefix, &pre_oid) ||
35723572
read_blob_object(&buf, &pre_oid, patch->old_mode))
3573-
return error(_("repository lacks the necessary blob to fall back on 3-way merge."));
3573+
return error(_("repository lacks the necessary blob to perform 3-way merge."));
35743574

35753575
if (state->apply_verbosity > verbosity_silent)
3576-
fprintf(stderr, _("Falling back to three-way merge...\n"));
3576+
fprintf(stderr, _("Performing three-way merge...\n"));
35773577

35783578
img = strbuf_detach(&buf, &len);
35793579
prepare_image(&tmp_image, img, len, 1);
@@ -3605,7 +3605,7 @@ static int try_threeway(struct apply_state *state,
36053605
if (status < 0) {
36063606
if (state->apply_verbosity > verbosity_silent)
36073607
fprintf(stderr,
3608-
_("Failed to fall back on three-way merge...\n"));
3608+
_("Failed to perform three-way merge...\n"));
36093609
return status;
36103610
}
36113611

@@ -3638,10 +3638,9 @@ static int apply_data(struct apply_state *state, struct patch *patch,
36383638
if (load_preimage(state, &image, patch, st, ce) < 0)
36393639
return -1;
36403640

3641-
if (patch->direct_to_threeway ||
3642-
apply_fragments(state, &image, patch) < 0) {
3641+
if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) {
36433642
/* Note: with --reject, apply_fragments() returns 0 */
3644-
if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0)
3643+
if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0)
36453644
return -1;
36463645
}
36473646
patch->result = image.buf;
@@ -5018,7 +5017,7 @@ int apply_parse_options(int argc, const char **argv,
50185017
OPT_BOOL(0, "apply", force_apply,
50195018
N_("also apply the patch (use with --stat/--summary/--check)")),
50205019
OPT_BOOL('3', "3way", &state->threeway,
5021-
N_( "attempt three-way merge if a patch does not apply")),
5020+
N_( "attempt three-way merge, fall back on normal patch if that fails")),
50225021
OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor,
50235022
N_("build a temporary index based on embedded index information")),
50245023
/* Think twice before adding "--nul" synonym to this */

t/t4108-apply-threeway.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,24 @@ test_expect_success 'apply -3 with add/add conflict (dirty working tree)' '
160160
test_cmp three.save three
161161
'
162162

163+
test_expect_success 'apply -3 with ambiguous repeating file' '
164+
git reset --hard &&
165+
test_write_lines 1 2 1 2 1 2 1 2 1 2 1 >one_two_repeat &&
166+
git add one_two_repeat &&
167+
git commit -m "init one" &&
168+
test_write_lines 1 2 1 2 1 2 1 2 one 2 1 >one_two_repeat &&
169+
git commit -a -m "change one" &&
170+
171+
git diff HEAD~ >Repeat.diff &&
172+
git reset --hard HEAD~ &&
173+
174+
test_write_lines 1 2 1 2 1 2 one 2 1 2 one >one_two_repeat &&
175+
git commit -a -m "change surrounding one" &&
176+
177+
git apply --index --3way Repeat.diff &&
178+
test_write_lines 1 2 1 2 1 2 one 2 one 2 one >expect &&
179+
180+
test_cmp expect one_two_repeat
181+
'
182+
163183
test_done

0 commit comments

Comments
 (0)