unpack-trees: fix oneway_merge accidentally carry over stage index

Phillip found out that 'git checkout -f <branch>' does not restore conflict/unmerged files correctly. All tracked files should be taken from <branch> and all non-zero stages removed. Most of this is true, except that the final file could be in stage one instead of zero. "checkout -f" (among other commands) does this with one-way merge, which is supposed to take stat info from the index and everything else from the given tree. The add_entry(.., old, ...) call in oneway_merge() though will keep stage index from the index. This is normally not a problem if the entry from the index is normal (stage #0). But if there is a conflict, stage #0 does not exist and we'll get stage #1 entry as "old" variable, which gets recorded in the final index. Fix it by clearing stage mask. This bug probably comes from b5b425074e (git-read-tree: make one-way merge also honor the "update" flag, 2005-06-07). Before this commit, we may create the final ("dst") index entry from the one in index, but we do clear CE_STAGEMASK. I briefly checked two- and three-way merge functions. I think we don't have the same problem in those. Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 18, 2019 at 18:38 UTC ab5af825db8beaf623f8ac95e4b4179ec1b9569e
2 files changed +25 -1
t/t2023-checkout-m.sh
+24
@@ -46,4 +46,28 @@ test_expect_success '-m restores 3-way conflicted+resolved file' '
46 test_cmp both.txt.conflicted.cleaned both.txt.cleaned
47 '
48
49 +test_expect_success 'force checkout a conflict file creates stage zero entry' '
50 + git init co-force &&
51 + (
52 + cd co-force &&
53 + echo a >a &&
54 + git add a &&
55 + git commit -ama &&
56 + A_OBJ=$(git rev-parse :a) &&
57 + git branch topic &&
58 + echo b >a &&
59 + git commit -amb &&
60 + B_OBJ=$(git rev-parse :a) &&
61 + git checkout topic &&
62 + echo c >a &&
63 + C_OBJ=$(git hash-object a) &&
64 + git checkout -m master &&
65 + test_cmp_rev :1:a $A_OBJ &&
66 + test_cmp_rev :2:a $B_OBJ &&
67 + test_cmp_rev :3:a $C_OBJ &&
68 + git checkout -f topic &&
69 + test_cmp_rev :0:a $A_OBJ
70 + )
71 +'
72 +
73 test_done
unpack-trees.c
+1 -1
@@ -2386,7 +2386,7 @@ int oneway_merge(const struct cache_entry * const *src,
2386 if (o->update && S_ISGITLINK(old->ce_mode) &&
2387 should_update_submodules() && !verify_uptodate(old, o))
2388 update |= CE_UPDATE;
2389 - add_entry(o, old, update, 0);
2389 + add_entry(o, old, update, CE_STAGEMASK);
2390 return 0;
2391 }
2392 return merged_entry(a, old, o);