directory rename detection: files/directories in the way of some renames

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 c449947a79d551be3fbef807c662d701df1c6b4f
1 file changed +330
t/t6043-merge-rename-directories.sh
+330
@@ -850,4 +850,334 @@ test_expect_success '4a-check: Directory split, with original directory still pr
850 # detection.) But, sadly, see testcase 8b.
851 ###########################################################################
852
853 +
854 +###########################################################################
855 +# SECTION 5: Files/directories in the way of subset of to-be-renamed paths
856 +#
857 +# Implicitly renaming files due to a detected directory rename could run
858 +# into problems if there are files or directories in the way of the paths
859 +# we want to rename. Explore such cases in this section.
860 +###########################################################################
861 +
862 +# Testcase 5a, Merge directories, other side adds files to original and target
863 +# Commit O: z/{b,c}, y/d
864 +# Commit A: z/{b,c,e_1,f}, y/{d,e_2}
865 +# Commit B: y/{b,c,d}
866 +# Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning
867 +# NOTE: While directory rename detection is active here causing z/f to
868 +# become y/f, we did not apply this for z/e_1 because that would
869 +# give us an add/add conflict for y/e_1 vs y/e_2. This problem with
870 +# this add/add, is that both versions of y/e are from the same side
871 +# of history, giving us no way to represent this conflict in the
872 +# index.
873 +
874 +test_expect_success '5a-setup: Merge directories, other side adds files to original and target' '
875 + test_create_repo 5a &&
876 + (
877 + cd 5a &&
878 +
879 + mkdir z &&
880 + echo b >z/b &&
881 + echo c >z/c &&
882 + mkdir y &&
883 + echo d >y/d &&
884 + git add z y &&
885 + test_tick &&
886 + git commit -m "O" &&
887 +
888 + git branch O &&
889 + git branch A &&
890 + git branch B &&
891 +
892 + git checkout A &&
893 + echo e1 >z/e &&
894 + echo f >z/f &&
895 + echo e2 >y/e &&
896 + git add z/e z/f y/e &&
897 + test_tick &&
898 + git commit -m "A" &&
899 +
900 + git checkout B &&
901 + git mv z/b y/ &&
902 + git mv z/c y/ &&
903 + rmdir z &&
904 + test_tick &&
905 + git commit -m "B"
906 + )
907 +'
908 +
909 +test_expect_failure '5a-check: Merge directories, other side adds files to original and target' '
910 + (
911 + cd 5a &&
912 +
913 + git checkout A^0 &&
914 +
915 + test_must_fail git merge -s recursive B^0 >out &&
916 + test_i18ngrep "CONFLICT.*implicit dir rename" out &&
917 +
918 + git ls-files -s >out &&
919 + test_line_count = 6 out &&
920 + git ls-files -u >out &&
921 + test_line_count = 0 out &&
922 + git ls-files -o >out &&
923 + test_line_count = 1 out &&
924 +
925 + git rev-parse >actual \
926 + :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f &&
927 + git rev-parse >expect \
928 + O:z/b O:z/c O:y/d A:y/e A:z/e A:z/f &&
929 + test_cmp expect actual
930 + )
931 +'
932 +
933 +# Testcase 5b, Rename/delete in order to get add/add/add conflict
934 +# (Related to testcase 8d; these may appear slightly inconsistent to users;
935 +# Also related to testcases 7d and 7e)
936 +# Commit O: z/{b,c,d_1}
937 +# Commit A: y/{b,c,d_2}
938 +# Commit B: z/{b,c,d_1,e}, y/d_3
939 +# Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3)
940 +# NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as
941 +# we normaly would since z/ is being renamed to y/, then this would be
942 +# a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add
943 +# conflict of y/d_1 vs. y/d_2 vs. y/d_3. Add/add/add is not
944 +# representable in the index, so the existence of y/d_3 needs to
945 +# cause us to bail on directory rename detection for that path, falling
946 +# back to git behavior without the directory rename detection.
947 +
948 +test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' '
949 + test_create_repo 5b &&
950 + (
951 + cd 5b &&
952 +
953 + mkdir z &&
954 + echo b >z/b &&
955 + echo c >z/c &&
956 + echo d1 >z/d &&
957 + git add z &&
958 + test_tick &&
959 + git commit -m "O" &&
960 +
961 + git branch O &&
962 + git branch A &&
963 + git branch B &&
964 +
965 + git checkout A &&
966 + git rm z/d &&
967 + git mv z y &&
968 + echo d2 >y/d &&
969 + git add y/d &&
970 + test_tick &&
971 + git commit -m "A" &&
972 +
973 + git checkout B &&
974 + mkdir y &&
975 + echo d3 >y/d &&
976 + echo e >z/e &&
977 + git add y/d z/e &&
978 + test_tick &&
979 + git commit -m "B"
980 + )
981 +'
982 +
983 +test_expect_failure '5b-check: Rename/delete in order to get add/add/add conflict' '
984 + (
985 + cd 5b &&
986 +
987 + git checkout A^0 &&
988 +
989 + test_must_fail git merge -s recursive B^0 >out &&
990 + test_i18ngrep "CONFLICT (add/add).* y/d" out &&
991 +
992 + git ls-files -s >out &&
993 + test_line_count = 5 out &&
994 + git ls-files -u >out &&
995 + test_line_count = 2 out &&
996 + git ls-files -o >out &&
997 + test_line_count = 1 out &&
998 +
999 + git rev-parse >actual \
1000 + :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d &&
1001 + git rev-parse >expect \
1002 + O:z/b O:z/c B:z/e A:y/d B:y/d &&
1003 + test_cmp expect actual &&
1004 +
1005 + test_must_fail git rev-parse :1:y/d &&
1006 + test_path_is_file y/d
1007 + )
1008 +'
1009 +
1010 +# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add
1011 +# (Directory rename detection would result in transitive rename vs.
1012 +# rename/rename(1to2) and turn it into a rename/rename(1to3). Further,
1013 +# rename paths conflict with separate adds on the other side)
1014 +# (Related to testcases 3b and 7c)
1015 +# Commit O: z/{b,c}, x/d_1
1016 +# Commit A: y/{b,c,d_2}, w/d_1
1017 +# Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4
1018 +# Expected: A mess, but only a rename/rename(1to2)/add/add mess. Use the
1019 +# presence of y/d_4 in B to avoid doing transitive rename of
1020 +# x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at
1021 +# y/d are y/d_2 and y/d_4. We still do the move from z/e to y/e,
1022 +# though, because it doesn't have anything in the way.
1023 +
1024 +test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' '
1025 + test_create_repo 5c &&
1026 + (
1027 + cd 5c &&
1028 +
1029 + mkdir z &&
1030 + echo b >z/b &&
1031 + echo c >z/c &&
1032 + mkdir x &&
1033 + echo d1 >x/d &&
1034 + git add z x &&
1035 + test_tick &&
1036 + git commit -m "O" &&
1037 +
1038 + git branch O &&
1039 + git branch A &&
1040 + git branch B &&
1041 +
1042 + git checkout A &&
1043 + git mv z y &&
1044 + echo d2 >y/d &&
1045 + git add y/d &&
1046 + git mv x w &&
1047 + test_tick &&
1048 + git commit -m "A" &&
1049 +
1050 + git checkout B &&
1051 + git mv x/d z/ &&
1052 + mkdir w &&
1053 + mkdir y &&
1054 + echo d3 >w/d &&
1055 + echo d4 >y/d &&
1056 + echo e >z/e &&
1057 + git add w/ y/ z/e &&
1058 + test_tick &&
1059 + git commit -m "B"
1060 + )
1061 +'
1062 +
1063 +test_expect_failure '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' '
1064 + (
1065 + cd 5c &&
1066 +
1067 + git checkout A^0 &&
1068 +
1069 + test_must_fail git merge -s recursive B^0 >out &&
1070 + test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out &&
1071 + test_i18ngrep "CONFLICT (add/add).* y/d" out &&
1072 +
1073 + git ls-files -s >out &&
1074 + test_line_count = 9 out &&
1075 + git ls-files -u >out &&
1076 + test_line_count = 6 out &&
1077 + git ls-files -o >out &&
1078 + test_line_count = 3 out &&
1079 +
1080 + git rev-parse >actual \
1081 + :0:y/b :0:y/c :0:y/e &&
1082 + git rev-parse >expect \
1083 + O:z/b O:z/c B:z/e &&
1084 + test_cmp expect actual &&
1085 +
1086 + test_must_fail git rev-parse :1:y/d &&
1087 + git rev-parse >actual \
1088 + :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d &&
1089 + git rev-parse >expect \
1090 + O:x/d B:w/d O:x/d A:y/d B:y/d O:x/d &&
1091 + test_cmp expect actual &&
1092 +
1093 + git hash-object >actual \
1094 + w/d~HEAD w/d~B^0 z/d &&
1095 + git rev-parse >expect \
1096 + O:x/d B:w/d O:x/d &&
1097 + test_cmp expect actual &&
1098 + test_path_is_missing x/d &&
1099 + test_path_is_file y/d &&
1100 + grep -q "<<<<" y/d # conflict markers should be present
1101 + )
1102 +'
1103 +
1104 +# Testcase 5d, Directory/file/file conflict due to directory rename
1105 +# Commit O: z/{b,c}
1106 +# Commit A: y/{b,c,d_1}
1107 +# Commit B: z/{b,c,d_2,f}, y/d/e
1108 +# Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD
1109 +# Note: The fact that y/d/ exists in B makes us bail on directory rename
1110 +# detection for z/d_2, but that doesn't prevent us from applying the
1111 +# directory rename detection for z/f -> y/f.
1112 +
1113 +test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' '
1114 + test_create_repo 5d &&
1115 + (
1116 + cd 5d &&
1117 +
1118 + mkdir z &&
1119 + echo b >z/b &&
1120 + echo c >z/c &&
1121 + git add z &&
1122 + test_tick &&
1123 + git commit -m "O" &&
1124 +
1125 + git branch O &&
1126 + git branch A &&
1127 + git branch B &&
1128 +
1129 + git checkout A &&
1130 + git mv z y &&
1131 + echo d1 >y/d &&
1132 + git add y/d &&
1133 + test_tick &&
1134 + git commit -m "A" &&
1135 +
1136 + git checkout B &&
1137 + mkdir -p y/d &&
1138 + echo e >y/d/e &&
1139 + echo d2 >z/d &&
1140 + echo f >z/f &&
1141 + git add y/d/e z/d z/f &&
1142 + test_tick &&
1143 + git commit -m "B"
1144 + )
1145 +'
1146 +
1147 +test_expect_failure '5d-check: Directory/file/file conflict due to directory rename' '
1148 + (
1149 + cd 5d &&
1150 +
1151 + git checkout A^0 &&
1152 +
1153 + test_must_fail git merge -s recursive B^0 >out &&
1154 + test_i18ngrep "CONFLICT (file/directory).*y/d" out &&
1155 +
1156 + git ls-files -s >out &&
1157 + test_line_count = 6 out &&
1158 + git ls-files -u >out &&
1159 + test_line_count = 1 out &&
1160 + git ls-files -o >out &&
1161 + test_line_count = 2 out &&
1162 +
1163 + git rev-parse >actual \
1164 + :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e &&
1165 + git rev-parse >expect \
1166 + O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e &&
1167 + test_cmp expect actual &&
1168 +
1169 + git hash-object y/d~HEAD >actual &&
1170 + git rev-parse A:y/d >expect &&
1171 + test_cmp expect actual
1172 + )
1173 +'
1174 +
1175 +###########################################################################
1176 +# Rules suggested by section 5:
1177 +#
1178 +# If a subset of to-be-renamed files have a file or directory in the way,
1179 +# "turn off" the directory rename for those specific sub-paths, falling
1180 +# back to old handling. But, sadly, see testcases 8a and 8b.
1181 +###########################################################################
1182 +
1183 test_done