directory rename detection: testcases exploring possibly suboptimal merges
Reviewed-by: Stefan Beller <sbeller@google.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Elijah Newren committed
Apr 19, 2018 at 10:57 UTC
362ab315acce6a4bf9849ffb6de76b3ebe381371
1 file changed
+404
t/t6043-merge-rename-directories.sh
+404
@@ -1912,4 +1912,408 @@ test_expect_failure '7e-check: transitive rename in rename/delete AND dirs in th
1912
)
1913
'
1914
1915
+###########################################################################
1916
+# SECTION 8: Suboptimal merges
1917
+#
1918
+# As alluded to in the last section, the ruleset we have built up for
1919
+# detecting directory renames unfortunately has some special cases where it
1920
+# results in slightly suboptimal or non-intuitive behavior. This section
1921
+# explores these cases.
1922
+#
1923
+# To be fair, we already had non-intuitive or suboptimal behavior for most
1924
+# of these cases in git before introducing implicit directory rename
1925
+# detection, but it'd be nice if there was a modified ruleset out there
1926
+# that handled these cases a bit better.
1927
+###########################################################################
1928
+
1929
+# Testcase 8a, Dual-directory rename, one into the others' way
1930
+# Commit O. x/{a,b}, y/{c,d}
1931
+# Commit A. x/{a,b,e}, y/{c,d,f}
1932
+# Commit B. y/{a,b}, z/{c,d}
1933
+#
1934
+# Possible Resolutions:
1935
+# w/o dir-rename detection: y/{a,b,f}, z/{c,d}, x/e
1936
+# Currently expected: y/{a,b,e,f}, z/{c,d}
1937
+# Optimal: y/{a,b,e}, z/{c,d,f}
1938
+#
1939
+# Note: Both x and y got renamed and it'd be nice to detect both, and we do
1940
+# better with directory rename detection than git did without, but the
1941
+# simple rule from section 5 prevents me from handling this as optimally as
1942
+# we potentially could.
1943
+
1944
+test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
1945
+ test_create_repo 8a &&
1946
+ (
1947
+ cd 8a &&
1948
+
1949
+ mkdir x &&
1950
+ mkdir y &&
1951
+ echo a >x/a &&
1952
+ echo b >x/b &&
1953
+ echo c >y/c &&
1954
+ echo d >y/d &&
1955
+ git add x y &&
1956
+ test_tick &&
1957
+ git commit -m "O" &&
1958
+
1959
+ git branch O &&
1960
+ git branch A &&
1961
+ git branch B &&
1962
+
1963
+ git checkout A &&
1964
+ echo e >x/e &&
1965
+ echo f >y/f &&
1966
+ git add x/e y/f &&
1967
+ test_tick &&
1968
+ git commit -m "A" &&
1969
+
1970
+ git checkout B &&
1971
+ git mv y z &&
1972
+ git mv x y &&
1973
+ test_tick &&
1974
+ git commit -m "B"
1975
+ )
1976
+'
1977
+
1978
+test_expect_failure '8a-check: Dual-directory rename, one into the others way' '
1979
+ (
1980
+ cd 8a &&
1981
+
1982
+ git checkout A^0 &&
1983
+
1984
+ git merge -s recursive B^0 &&
1985
+
1986
+ git ls-files -s >out &&
1987
+ test_line_count = 6 out &&
1988
+ git ls-files -u >out &&
1989
+ test_line_count = 0 out &&
1990
+ git ls-files -o >out &&
1991
+ test_line_count = 1 out &&
1992
+
1993
+ git rev-parse >actual \
1994
+ HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d &&
1995
+ git rev-parse >expect \
1996
+ O:x/a O:x/b A:x/e A:y/f O:y/c O:y/d &&
1997
+ test_cmp expect actual
1998
+ )
1999
+'
2000
+
2001
+# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames
2002
+# Commit O. x/{a_1,b_1}, y/{a_2,b_2}
2003
+# Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2}
2004
+# Commit B. y/{a_1,b_1}, z/{a_2,b_2}
2005
+#
2006
+# w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1
2007
+# Currently expected: <same>
2008
+# Scary: y/{a_1,b_1}, z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2)
2009
+# Optimal: y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2}
2010
+#
2011
+# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and
2012
+# y, both are named 'e'. Without directory rename detection, neither file
2013
+# moves directories. Implement directory rename detection suboptimally, and
2014
+# you get an add/add conflict, but both files were added in commit A, so this
2015
+# is an add/add conflict where one side of history added both files --
2016
+# something we can't represent in the index. Obviously, we'd prefer the last
2017
+# resolution, but our previous rules are too coarse to allow it. Using both
2018
+# the rules from section 4 and section 5 save us from the Scary resolution,
2019
+# making us fall back to pre-directory-rename-detection behavior for both
2020
+# e_1 and e_2.
2021
+
2022
+test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' '
2023
+ test_create_repo 8b &&
2024
+ (
2025
+ cd 8b &&
2026
+
2027
+ mkdir x &&
2028
+ mkdir y &&
2029
+ echo a1 >x/a &&
2030
+ echo b1 >x/b &&
2031
+ echo a2 >y/a &&
2032
+ echo b2 >y/b &&
2033
+ git add x y &&
2034
+ test_tick &&
2035
+ git commit -m "O" &&
2036
+
2037
+ git branch O &&
2038
+ git branch A &&
2039
+ git branch B &&
2040
+
2041
+ git checkout A &&
2042
+ echo e1 >x/e &&
2043
+ echo e2 >y/e &&
2044
+ git add x/e y/e &&
2045
+ test_tick &&
2046
+ git commit -m "A" &&
2047
+
2048
+ git checkout B &&
2049
+ git mv y z &&
2050
+ git mv x y &&
2051
+ test_tick &&
2052
+ git commit -m "B"
2053
+ )
2054
+'
2055
+
2056
+test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' '
2057
+ (
2058
+ cd 8b &&
2059
+
2060
+ git checkout A^0 &&
2061
+
2062
+ git merge -s recursive B^0 &&
2063
+
2064
+ git ls-files -s >out &&
2065
+ test_line_count = 6 out &&
2066
+ git ls-files -u >out &&
2067
+ test_line_count = 0 out &&
2068
+ git ls-files -o >out &&
2069
+ test_line_count = 1 out &&
2070
+
2071
+ git rev-parse >actual \
2072
+ HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e &&
2073
+ git rev-parse >expect \
2074
+ O:x/a O:x/b O:y/a O:y/b A:x/e A:y/e &&
2075
+ test_cmp expect actual
2076
+ )
2077
+'
2078
+
2079
+# Testcase 8c, rename+modify/delete
2080
+# (Related to testcases 5b and 8d)
2081
+# Commit O: z/{b,c,d}
2082
+# Commit A: y/{b,c}
2083
+# Commit B: z/{b,c,d_modified,e}
2084
+# Expected: y/{b,c,e}, CONFLICT(rename+modify/delete: x/d -> y/d or deleted)
2085
+#
2086
+# Note: This testcase doesn't present any concerns for me...until you
2087
+# compare it with testcases 5b and 8d. See notes in 8d for more
2088
+# details.
2089
+
2090
+test_expect_success '8c-setup: rename+modify/delete' '
2091
+ test_create_repo 8c &&
2092
+ (
2093
+ cd 8c &&
2094
+
2095
+ mkdir z &&
2096
+ echo b >z/b &&
2097
+ echo c >z/c &&
2098
+ test_seq 1 10 >z/d &&
2099
+ git add z &&
2100
+ test_tick &&
2101
+ git commit -m "O" &&
2102
+
2103
+ git branch O &&
2104
+ git branch A &&
2105
+ git branch B &&
2106
+
2107
+ git checkout A &&
2108
+ git rm z/d &&
2109
+ git mv z y &&
2110
+ test_tick &&
2111
+ git commit -m "A" &&
2112
+
2113
+ git checkout B &&
2114
+ echo 11 >z/d &&
2115
+ test_chmod +x z/d &&
2116
+ echo e >z/e &&
2117
+ git add z/d z/e &&
2118
+ test_tick &&
2119
+ git commit -m "B"
2120
+ )
2121
+'
2122
+
2123
+test_expect_failure '8c-check: rename+modify/delete' '
2124
+ (
2125
+ cd 8c &&
2126
+
2127
+ git checkout A^0 &&
2128
+
2129
+ test_must_fail git merge -s recursive B^0 >out &&
2130
+ test_i18ngrep "CONFLICT (rename/delete).* z/d.*y/d" out &&
2131
+
2132
+ git ls-files -s >out &&
2133
+ test_line_count = 4 out &&
2134
+ git ls-files -u >out &&
2135
+ test_line_count = 1 out &&
2136
+ git ls-files -o >out &&
2137
+ test_line_count = 1 out &&
2138
+
2139
+ git rev-parse >actual \
2140
+ :0:y/b :0:y/c :0:y/e :3:y/d &&
2141
+ git rev-parse >expect \
2142
+ O:z/b O:z/c B:z/e B:z/d &&
2143
+ test_cmp expect actual &&
2144
+
2145
+ test_must_fail git rev-parse :1:y/d &&
2146
+ test_must_fail git rev-parse :2:y/d &&
2147
+ git ls-files -s y/d | grep ^100755 &&
2148
+ test_path_is_file y/d
2149
+ )
2150
+'
2151
+
2152
+# Testcase 8d, rename/delete...or not?
2153
+# (Related to testcase 5b; these may appear slightly inconsistent to users;
2154
+# Also related to testcases 7d and 7e)
2155
+# Commit O: z/{b,c,d}
2156
+# Commit A: y/{b,c}
2157
+# Commit B: z/{b,c,d,e}
2158
+# Expected: y/{b,c,e}
2159
+#
2160
+# Note: It would also be somewhat reasonable to resolve this as
2161
+# y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted)
2162
+# The logic being that the only difference between this testcase and 8c
2163
+# is that there is no modification to d. That suggests that instead of a
2164
+# rename/modify vs. delete conflict, we should just have a rename/delete
2165
+# conflict, otherwise we are being inconsistent.
2166
+#
2167
+# However...as far as consistency goes, we didn't report a conflict for
2168
+# path d_1 in testcase 5b due to a different file being in the way. So,
2169
+# we seem to be forced to have cases where users can change things
2170
+# slightly and get what they may perceive as inconsistent results. It
2171
+# would be nice to avoid that, but I'm not sure I see how.
2172
+#
2173
+# In this case, I'm leaning towards: commit A was the one that deleted z/d
2174
+# and it did the rename of z to y, so the two "conflicts" (rename vs.
2175
+# delete) are both coming from commit A, which is illogical. Conflicts
2176
+# during merging are supposed to be about opposite sides doing things
2177
+# differently.
2178
+
2179
+test_expect_success '8d-setup: rename/delete...or not?' '
2180
+ test_create_repo 8d &&
2181
+ (
2182
+ cd 8d &&
2183
+
2184
+ mkdir z &&
2185
+ echo b >z/b &&
2186
+ echo c >z/c &&
2187
+ test_seq 1 10 >z/d &&
2188
+ git add z &&
2189
+ test_tick &&
2190
+ git commit -m "O" &&
2191
+
2192
+ git branch O &&
2193
+ git branch A &&
2194
+ git branch B &&
2195
+
2196
+ git checkout A &&
2197
+ git rm z/d &&
2198
+ git mv z y &&
2199
+ test_tick &&
2200
+ git commit -m "A" &&
2201
+
2202
+ git checkout B &&
2203
+ echo e >z/e &&
2204
+ git add z/e &&
2205
+ test_tick &&
2206
+ git commit -m "B"
2207
+ )
2208
+'
2209
+
2210
+test_expect_failure '8d-check: rename/delete...or not?' '
2211
+ (
2212
+ cd 8d &&
2213
+
2214
+ git checkout A^0 &&
2215
+
2216
+ git merge -s recursive B^0 &&
2217
+
2218
+ git ls-files -s >out &&
2219
+ test_line_count = 3 out &&
2220
+
2221
+ git rev-parse >actual \
2222
+ HEAD:y/b HEAD:y/c HEAD:y/e &&
2223
+ git rev-parse >expect \
2224
+ O:z/b O:z/c B:z/e &&
2225
+ test_cmp expect actual
2226
+ )
2227
+'
2228
+
2229
+# Testcase 8e, Both sides rename, one side adds to original directory
2230
+# Commit O: z/{b,c}
2231
+# Commit A: y/{b,c}
2232
+# Commit B: w/{b,c}, z/d
2233
+#
2234
+# Possible Resolutions:
2235
+# w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b),
2236
+# CONFLICT(z/c -> y/c vs. w/c)
2237
+# Currently expected: y/d, CONFLICT(z/b -> y/b vs. w/b),
2238
+# CONFLICT(z/c -> y/c vs. w/c)
2239
+# Optimal: ??
2240
+#
2241
+# Notes: In commit A, directory z got renamed to y. In commit B, directory z
2242
+# did NOT get renamed; the directory is still present; instead it is
2243
+# considered to have just renamed a subset of paths in directory z
2244
+# elsewhere. Therefore, the directory rename done in commit A to z/
2245
+# applies to z/d and maps it to y/d.
2246
+#
2247
+# It's possible that users would get confused about this, but what
2248
+# should we do instead? Silently leaving at z/d seems just as bad or
2249
+# maybe even worse. Perhaps we could print a big warning about z/d
2250
+# and how we're moving to y/d in this case, but when I started thinking
2251
+# about the ramifications of doing that, I didn't know how to rule out
2252
+# that opening other weird edge and corner cases so I just punted.
2253
+
2254
+test_expect_success '8e-setup: Both sides rename, one side adds to original directory' '
2255
+ test_create_repo 8e &&
2256
+ (
2257
+ cd 8e &&
2258
+
2259
+ mkdir z &&
2260
+ echo b >z/b &&
2261
+ echo c >z/c &&
2262
+ git add z &&
2263
+ test_tick &&
2264
+ git commit -m "O" &&
2265
+
2266
+ git branch O &&
2267
+ git branch A &&
2268
+ git branch B &&
2269
+
2270
+ git checkout A &&
2271
+ git mv z y &&
2272
+ test_tick &&
2273
+ git commit -m "A" &&
2274
+
2275
+ git checkout B &&
2276
+ git mv z w &&
2277
+ mkdir z &&
2278
+ echo d >z/d &&
2279
+ git add z/d &&
2280
+ test_tick &&
2281
+ git commit -m "B"
2282
+ )
2283
+'
2284
+
2285
+test_expect_failure '8e-check: Both sides rename, one side adds to original directory' '
2286
+ (
2287
+ cd 8e &&
2288
+
2289
+ git checkout A^0 &&
2290
+
2291
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
2292
+ test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out &&
2293
+ test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out &&
2294
+
2295
+ git ls-files -s >out &&
2296
+ test_line_count = 7 out &&
2297
+ git ls-files -u >out &&
2298
+ test_line_count = 6 out &&
2299
+ git ls-files -o >out &&
2300
+ test_line_count = 2 out &&
2301
+
2302
+ git rev-parse >actual \
2303
+ :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d &&
2304
+ git rev-parse >expect \
2305
+ O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d &&
2306
+ test_cmp expect actual &&
2307
+
2308
+ git hash-object >actual \
2309
+ y/b w/b y/c w/c &&
2310
+ git rev-parse >expect \
2311
+ O:z/b O:z/b O:z/c O:z/c &&
2312
+ test_cmp expect actual &&
2313
+
2314
+ test_path_is_missing z/b &&
2315
+ test_path_is_missing z/c
2316
+ )
2317
+'
2318
+
2319
test_done