git-apply: skip threeway in add / rename cases

Certain invocations of "git apply --3way" will attempt threeway and fail due to missing objects, even though git is able to fall back on apply_fragments and apply the patch successfully with a return value of 0. To fix, return early from try_threeway() in the following cases: - When the patch is a rename and no lines have changed. In this case, "git diff" doesn't record the blob info, so 3way is neither possible nor necessary. - When the patch is an addition and there is no add/add conflict, i.e. direct_to_threeway is false. In this case, threeway will fail since the preimage is not in cache, but isn't necessary anyway since there is no conflict. This fixes a few unecessary error messages when applying these kinds of patches with --3way. It also fixes a reported issue where applying a concatenation of several git produced patches will fail when those patches involve a deletion followed by creation of the same file. Add a test for this case too. (test provided by <i@zenithal.me>) Signed-off-by: Jerry Zhang <jerry@skydio.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jerry Zhang committed Dec 17, 2021 at 15:29 UTC 34d607032c0db6a6c68754e7a339c3caf08d6a79
2 files changed +21 -1
apply.c
+3 -1
@@ -3582,7 +3582,9 @@ static int try_threeway(struct apply_state *state,
3582
3583 /* No point falling back to 3-way merge in these cases */
3584 if (patch->is_delete ||
3585 - S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
3585 + S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode) ||
3586 + (patch->is_new && !patch->direct_to_threeway) ||
3587 + (patch->is_rename && !patch->lines_added && !patch->lines_deleted))
3588 return -1;
3589
3590 /* Preimage the patch was prepared for */
t/t4108-apply-threeway.sh
+18
@@ -275,4 +275,22 @@ test_expect_success 'apply full-index patch with 3way' '
275 git apply --3way --index bin.diff
276 '
277
278 +test_expect_success 'apply delete then new patch with 3way' '
279 + git reset --hard main &&
280 + test_write_lines 2 > delnew &&
281 + git add delnew &&
282 + git diff --cached >> new.patch &&
283 + git reset --hard &&
284 + test_write_lines 1 > delnew &&
285 + git add delnew &&
286 + git commit -m "delnew" &&
287 + rm delnew &&
288 + git diff >> delete-then-new.patch &&
289 + cat new.patch >> delete-then-new.patch &&
290 +
291 + git checkout -- . &&
292 + # Apply must succeed.
293 + git apply --3way delete-then-new.patch
294 +'
295 +
296 test_done