t7527: add case-insensitve test for FSMonitor

The FSMonitor client code trusts the spelling of the pathnames in the FSEvents received from the FSMonitor daemon. On case-insensitive file systems, these OBSERVED pathnames may be spelled differently than the EXPECTED pathnames listed in the .git/index. This causes a miss when using `index_name_pos()` which expects the given case to be correct. When this happens, the FSMonitor client code does not update the state of the CE_FSMONITOR_VALID bit when refreshing the index (and before starting to scan the worktree). This results in modified files NOT being reported by `git status` when there is a discrepancy in the case-spelling of a tracked file's pathname. This commit contains a (rather contrived) test case to demonstrate this. A later commit in this series will update the FSMonitor client code to recognize these discrepancies and update the CE_ bit accordingly. Signed-off-by: Jeff Hostetler <jeffhostetler@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Feb 26, 2024 at 21:39 UTC 32ca706fadb947e7cb461f4c9780ccd233ef93d9
1 file changed +217
t/t7527-builtin-fsmonitor.sh
+217
@@ -1037,4 +1037,221 @@ test_expect_success 'split-index and FSMonitor work well together' '
1037 )
1038 '
1039
1040 +# The FSMonitor daemon reports the OBSERVED pathname of modified files
1041 +# and thus contains the OBSERVED spelling on case-insensitive file
1042 +# systems. The daemon does not (and should not) load the .git/index
1043 +# file and therefore does not know the expected case-spelling. Since
1044 +# it is possible for the user to create files/subdirectories with the
1045 +# incorrect case, a modified file event for a tracked will not have
1046 +# the EXPECTED case. This can cause `index_name_pos()` to incorrectly
1047 +# report that the file is untracked. This causes the client to fail to
1048 +# mark the file as possibly dirty (keeping the CE_FSMONITOR_VALID bit
1049 +# set) so that `git status` will avoid inspecting it and thus not
1050 +# present in the status output.
1051 +#
1052 +# The setup is a little contrived.
1053 +#
1054 +test_expect_failure CASE_INSENSITIVE_FS 'fsmonitor subdir case wrong on disk' '
1055 + test_when_finished "stop_daemon_delete_repo subdir_case_wrong" &&
1056 +
1057 + git init subdir_case_wrong &&
1058 + (
1059 + cd subdir_case_wrong &&
1060 + echo x >AAA &&
1061 + echo x >BBB &&
1062 +
1063 + mkdir dir1 &&
1064 + echo x >dir1/file1 &&
1065 + mkdir dir1/dir2 &&
1066 + echo x >dir1/dir2/file2 &&
1067 + mkdir dir1/dir2/dir3 &&
1068 + echo x >dir1/dir2/dir3/file3 &&
1069 +
1070 + echo x >yyy &&
1071 + echo x >zzz &&
1072 + git add . &&
1073 + git commit -m "data" &&
1074 +
1075 + # This will cause "dir1/" and everything under it
1076 + # to be deleted.
1077 + git sparse-checkout set --cone --sparse-index &&
1078 +
1079 + # Create dir2 with the wrong case and then let Git
1080 + # repopulate dir3 -- it will not correct the spelling
1081 + # of dir2.
1082 + mkdir dir1 &&
1083 + mkdir dir1/DIR2 &&
1084 + git sparse-checkout add dir1/dir2/dir3
1085 + ) &&
1086 +
1087 + start_daemon -C subdir_case_wrong --tf "$PWD/subdir_case_wrong.trace" &&
1088 +
1089 + # Enable FSMonitor in the client. Run enough commands for
1090 + # the .git/index to sync up with the daemon with everything
1091 + # marked clean.
1092 + git -C subdir_case_wrong config core.fsmonitor true &&
1093 + git -C subdir_case_wrong update-index --fsmonitor &&
1094 + git -C subdir_case_wrong status &&
1095 +
1096 + # Make some files dirty so that FSMonitor gets FSEvents for
1097 + # each of them.
1098 + echo xx >>subdir_case_wrong/AAA &&
1099 + echo xx >>subdir_case_wrong/dir1/DIR2/dir3/file3 &&
1100 + echo xx >>subdir_case_wrong/zzz &&
1101 +
1102 + GIT_TRACE_FSMONITOR="$PWD/subdir_case_wrong.log" \
1103 + git -C subdir_case_wrong --no-optional-locks status --short \
1104 + >"$PWD/subdir_case_wrong.out" &&
1105 +
1106 + # "git status" should have gotten file events for each of
1107 + # the 3 files.
1108 + #
1109 + # "dir2" should be in the observed case on disk.
1110 + grep "fsmonitor_refresh_callback" \
1111 + <"$PWD/subdir_case_wrong.log" \
1112 + >"$PWD/subdir_case_wrong.log1" &&
1113 +
1114 + grep -q "AAA.*pos 0" "$PWD/subdir_case_wrong.log1" &&
1115 + grep -q "zzz.*pos 6" "$PWD/subdir_case_wrong.log1" &&
1116 +
1117 + grep -q "dir1/DIR2/dir3/file3.*pos -3" "$PWD/subdir_case_wrong.log1" &&
1118 +
1119 + # The refresh-callbacks should have caused "git status" to clear
1120 + # the CE_FSMONITOR_VALID bit on each of those files and caused
1121 + # the worktree scan to visit them and mark them as modified.
1122 + grep -q " M AAA" "$PWD/subdir_case_wrong.out" &&
1123 + grep -q " M zzz" "$PWD/subdir_case_wrong.out" &&
1124 +
1125 + # Expect Breakage: with the case confusion, the "(pos -3)" causes
1126 + # the client to not clear the CE_FSMONITOR_VALID bit and therefore
1127 + # status will not rescan the file and therefore not report it as dirty.
1128 + grep -q " M dir1/dir2/dir3/file3" "$PWD/subdir_case_wrong.out"
1129 +'
1130 +
1131 +test_expect_failure CASE_INSENSITIVE_FS 'fsmonitor file case wrong on disk' '
1132 + test_when_finished "stop_daemon_delete_repo file_case_wrong" &&
1133 +
1134 + git init file_case_wrong &&
1135 + (
1136 + cd file_case_wrong &&
1137 + echo x >AAA &&
1138 + echo x >BBB &&
1139 +
1140 + mkdir dir1 &&
1141 + mkdir dir1/dir2 &&
1142 + mkdir dir1/dir2/dir3 &&
1143 + echo x >dir1/dir2/dir3/FILE-3-B &&
1144 + echo x >dir1/dir2/dir3/XXXX-3-X &&
1145 + echo x >dir1/dir2/dir3/file-3-a &&
1146 + echo x >dir1/dir2/dir3/yyyy-3-y &&
1147 + mkdir dir1/dir2/dir4 &&
1148 + echo x >dir1/dir2/dir4/FILE-4-A &&
1149 + echo x >dir1/dir2/dir4/XXXX-4-X &&
1150 + echo x >dir1/dir2/dir4/file-4-b &&
1151 + echo x >dir1/dir2/dir4/yyyy-4-y &&
1152 +
1153 + echo x >yyy &&
1154 + echo x >zzz &&
1155 + git add . &&
1156 + git commit -m "data"
1157 + ) &&
1158 +
1159 + start_daemon -C file_case_wrong --tf "$PWD/file_case_wrong.trace" &&
1160 +
1161 + # Enable FSMonitor in the client. Run enough commands for
1162 + # the .git/index to sync up with the daemon with everything
1163 + # marked clean.
1164 + git -C file_case_wrong config core.fsmonitor true &&
1165 + git -C file_case_wrong update-index --fsmonitor &&
1166 + git -C file_case_wrong status &&
1167 +
1168 + # Make some files dirty so that FSMonitor gets FSEvents for
1169 + # each of them.
1170 + echo xx >>file_case_wrong/AAA &&
1171 + echo xx >>file_case_wrong/zzz &&
1172 +
1173 + # Rename some files so that FSMonitor sees a create and delete
1174 + # FSEvent for each. (A simple "mv foo FOO" is not portable
1175 + # between macOS and Windows. It works on both platforms, but makes
1176 + # the test messy, since (1) one platform updates "ctime" on the
1177 + # moved file and one does not and (2) it causes a directory event
1178 + # on one platform and not on the other which causes additional
1179 + # scanning during "git status" which causes a "H" vs "h" discrepancy
1180 + # in "git ls-files -f".) So old-school it and move it out of the
1181 + # way and copy it to the case-incorrect name so that we get fresh
1182 + # "ctime" and "mtime" values.
1183 +
1184 + mv file_case_wrong/dir1/dir2/dir3/file-3-a file_case_wrong/dir1/dir2/dir3/ORIG &&
1185 + cp file_case_wrong/dir1/dir2/dir3/ORIG file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
1186 + rm file_case_wrong/dir1/dir2/dir3/ORIG &&
1187 + mv file_case_wrong/dir1/dir2/dir4/FILE-4-A file_case_wrong/dir1/dir2/dir4/ORIG &&
1188 + cp file_case_wrong/dir1/dir2/dir4/ORIG file_case_wrong/dir1/dir2/dir4/file-4-a &&
1189 + rm file_case_wrong/dir1/dir2/dir4/ORIG &&
1190 +
1191 + # Run status enough times to fully sync.
1192 + #
1193 + # The first instance should get the create and delete FSEvents
1194 + # for each pair. Status should update the index with a new FSM
1195 + # token (so the next invocation will not see data for these
1196 + # events).
1197 +
1198 + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try1.log" \
1199 + git -C file_case_wrong status --short \
1200 + >"$PWD/file_case_wrong-try1.out" &&
1201 + grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try1.log" &&
1202 + grep -q "fsmonitor_refresh_callback.*file-3-a.*pos 4" "$PWD/file_case_wrong-try1.log" &&
1203 + grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos 6" "$PWD/file_case_wrong-try1.log" &&
1204 + grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try1.log" &&
1205 +
1206 + # FSM refresh will have invalidated the FSM bit and cause a regular
1207 + # (real) scan of these tracked files, so they should have "H" status.
1208 + # (We will not see a "h" status until the next refresh (on the next
1209 + # command).)
1210 +
1211 + git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf1.out" &&
1212 + grep -q "H dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf1.out" &&
1213 + grep -q "H dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf1.out" &&
1214 +
1215 +
1216 + # Try the status again. We assume that the above status command
1217 + # advanced the token so that the next one will not see those events.
1218 +
1219 + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try2.log" \
1220 + git -C file_case_wrong status --short \
1221 + >"$PWD/file_case_wrong-try2.out" &&
1222 + ! grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos" "$PWD/file_case_wrong-try2.log" &&
1223 + ! grep -q "fsmonitor_refresh_callback.*file-3-a.*pos" "$PWD/file_case_wrong-try2.log" &&
1224 + ! grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos" "$PWD/file_case_wrong-try2.log" &&
1225 + ! grep -q "fsmonitor_refresh_callback.*file-4-a.*pos" "$PWD/file_case_wrong-try2.log" &&
1226 +
1227 + # FSM refresh saw nothing, so it will mark all files as valid,
1228 + # so they should now have "h" status.
1229 +
1230 + git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf2.out" &&
1231 + grep -q "h dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf2.out" &&
1232 + grep -q "h dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf2.out" &&
1233 +
1234 +
1235 + # We now have files with clean content, but with case-incorrect
1236 + # file names. Modify them to see if status properly reports
1237 + # them.
1238 +
1239 + echo xx >>file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
1240 + echo xx >>file_case_wrong/dir1/dir2/dir4/file-4-a &&
1241 +
1242 + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try3.log" \
1243 + git -C file_case_wrong --no-optional-locks status --short \
1244 + >"$PWD/file_case_wrong-try3.out" &&
1245 + # FSEvents are in observed case.
1246 + grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try3.log" &&
1247 + grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try3.log" &&
1248 +
1249 + # Expect Breakage: with the case confusion, the "(pos-3)" and
1250 + # "(pos -9)" causes the client to not clear the CE_FSMONITOR_VALID
1251 + # bit and therefore status will not rescan the files and therefore
1252 + # not report them as dirty.
1253 + grep -q " M dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-try3.out" &&
1254 + grep -q " M dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-try3.out"
1255 +'
1256 +
1257 test_done