| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='built-in file system watcher' |
| 4 | |
| 5 | . ./test-lib.sh |
| 6 | |
| 7 | if ! test_have_prereq FSMONITOR_DAEMON |
| 8 | then |
| 9 | skip_all="fsmonitor--daemon is not supported on this platform" |
| 10 | test_done |
| 11 | fi |
| 12 | |
| 13 | # Verify that the filesystem delivers events to the daemon. |
| 14 | # On some configurations (e.g., overlayfs with older kernels), |
| 15 | # inotify watches succeed but events are never delivered. The |
| 16 | # cookie wait will time out and the daemon logs a trace message. |
| 17 | # |
| 18 | # Use "timeout" (if available) to guard each step against hangs. |
| 19 | maybe_timeout () { |
| 20 | if type timeout >/dev/null 2>&1 |
| 21 | then |
| 22 | timeout "$@" |
| 23 | else |
| 24 | shift |
| 25 | "$@" |
| 26 | fi |
| 27 | } |
| 28 | |
| 29 | test_lazy_prereq FSMONITOR_WORKS ' |
| 30 | git init test_fsmonitor_smoke || return 1 |
| 31 | |
| 32 | GIT_TRACE_FSMONITOR="$PWD/smoke.trace" && |
| 33 | export GIT_TRACE_FSMONITOR && |
| 34 | maybe_timeout 30 \ |
| 35 | git -C test_fsmonitor_smoke fsmonitor--daemon start \ |
| 36 | --start-timeout=10 |
| 37 | ret=$? |
| 38 | unset GIT_TRACE_FSMONITOR |
| 39 | if test $ret -ne 0 |
| 40 | then |
| 41 | rm -rf test_fsmonitor_smoke smoke.trace |
| 42 | return 1 |
| 43 | fi |
| 44 | |
| 45 | maybe_timeout 10 \ |
| 46 | test-tool -C test_fsmonitor_smoke fsmonitor-client query \ |
| 47 | --token 0 >/dev/null 2>&1 |
| 48 | maybe_timeout 5 \ |
| 49 | git -C test_fsmonitor_smoke fsmonitor--daemon stop 2>/dev/null |
| 50 | ! grep -q "cookie_wait timed out" "$PWD/smoke.trace" 2>/dev/null |
| 51 | ret=$? |
| 52 | rm -rf test_fsmonitor_smoke smoke.trace |
| 53 | return $ret |
| 54 | ' |
| 55 | |
| 56 | if ! test_have_prereq FSMONITOR_WORKS |
| 57 | then |
| 58 | skip_all="filesystem does not deliver fsmonitor events (container/overlayfs?)" |
| 59 | test_done |
| 60 | fi |
| 61 | |
| 62 | stop_daemon_delete_repo () { |
| 63 | r=$1 && |
| 64 | { maybe_timeout 30 git -C $r fsmonitor--daemon stop 2>/dev/null || :; } && |
| 65 | rm -rf $1 |
| 66 | } |
| 67 | |
| 68 | start_daemon () { |
| 69 | r= tf= t2= tk= && |
| 70 | |
| 71 | while test "$#" -ne 0 |
| 72 | do |
| 73 | case "$1" in |
| 74 | -C) |
| 75 | r="-C ${2?}" |
| 76 | shift |
| 77 | ;; |
| 78 | --tf) |
| 79 | tf="${2?}" |
| 80 | shift |
| 81 | ;; |
| 82 | --t2) |
| 83 | t2="${2?}" |
| 84 | shift |
| 85 | ;; |
| 86 | --tk) |
| 87 | tk="${2?}" |
| 88 | shift |
| 89 | ;; |
| 90 | -*) |
| 91 | BUG "error: unknown option: '$1'" |
| 92 | ;; |
| 93 | *) |
| 94 | BUG "error: unbound argument: '$1'" |
| 95 | ;; |
| 96 | esac |
| 97 | shift |
| 98 | done && |
| 99 | |
| 100 | ( |
| 101 | if test -n "$tf" |
| 102 | then |
| 103 | GIT_TRACE_FSMONITOR="$tf" |
| 104 | export GIT_TRACE_FSMONITOR |
| 105 | fi && |
| 106 | |
| 107 | if test -n "$t2" |
| 108 | then |
| 109 | GIT_TRACE2_PERF="$t2" |
| 110 | export GIT_TRACE2_PERF |
| 111 | fi && |
| 112 | |
| 113 | if test -n "$tk" |
| 114 | then |
| 115 | GIT_TEST_FSMONITOR_TOKEN="$tk" |
| 116 | export GIT_TEST_FSMONITOR_TOKEN |
| 117 | fi && |
| 118 | |
| 119 | git $r fsmonitor--daemon start --start-timeout=10 && |
| 120 | git $r fsmonitor--daemon status |
| 121 | ) |
| 122 | } |
| 123 | |
| 124 | # Is a Trace2 data event present with the given catetory and key? |
| 125 | # We do not care what the value is. |
| 126 | # |
| 127 | have_t2_data_event () { |
| 128 | c=$1 && |
| 129 | k=$2 && |
| 130 | |
| 131 | grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' |
| 132 | } |
| 133 | |
| 134 | test_expect_success 'explicit daemon start and stop' ' |
| 135 | test_when_finished "stop_daemon_delete_repo test_explicit" && |
| 136 | |
| 137 | git init test_explicit && |
| 138 | start_daemon -C test_explicit && |
| 139 | |
| 140 | git -C test_explicit fsmonitor--daemon stop && |
| 141 | test_must_fail git -C test_explicit fsmonitor--daemon status |
| 142 | ' |
| 143 | |
| 144 | test_expect_success 'implicit daemon start' ' |
| 145 | test_when_finished "stop_daemon_delete_repo test_implicit" && |
| 146 | |
| 147 | git init test_implicit && |
| 148 | test_must_fail git -C test_implicit fsmonitor--daemon status && |
| 149 | |
| 150 | # query will implicitly start the daemon. |
| 151 | # |
| 152 | # for test-script simplicity, we send a V1 timestamp rather than |
| 153 | # a V2 token. either way, the daemon response to any query contains |
| 154 | # a new V2 token. (the daemon may complain that we sent a V1 request, |
| 155 | # but this test case is only concerned with whether the daemon was |
| 156 | # implicitly started.) |
| 157 | |
| 158 | GIT_TRACE2_EVENT="$PWD/.git/trace" \ |
| 159 | test-tool -C test_implicit fsmonitor-client query --token 0 >actual && |
| 160 | nul_to_q <actual >actual.filtered && |
| 161 | grep "builtin:" actual.filtered && |
| 162 | |
| 163 | # confirm that a daemon was started in the background. |
| 164 | # |
| 165 | # since the mechanism for starting the background daemon is platform |
| 166 | # dependent, just confirm that the foreground command received a |
| 167 | # response from the daemon. |
| 168 | |
| 169 | have_t2_data_event fsm_client query/response-length <.git/trace && |
| 170 | |
| 171 | git -C test_implicit fsmonitor--daemon status && |
| 172 | git -C test_implicit fsmonitor--daemon stop && |
| 173 | test_must_fail git -C test_implicit fsmonitor--daemon status |
| 174 | ' |
| 175 | |
| 176 | # Verify that the daemon has shutdown. Spin a few seconds to |
| 177 | # make the test a little more robust during CI testing. |
| 178 | # |
| 179 | # We're looking for an implicit shutdown, such as when we delete or |
| 180 | # rename the ".git" directory. Our delete/rename will cause a file |
| 181 | # system event that the daemon will see and the daemon will |
| 182 | # auto-shutdown as soon as it sees it. But this is racy with our `git |
| 183 | # fsmonitor--daemon status` commands (and we cannot use a cookie file |
| 184 | # here to help us). So spin a little and give the daemon a chance to |
| 185 | # see the event. (This is primarily for underpowered CI build/test |
| 186 | # machines (where it might take a moment to wake and reschedule the |
| 187 | # daemon process) to avoid false alarms during test runs.) |
| 188 | # |
| 189 | IMPLICIT_TIMEOUT=5 |
| 190 | |
| 191 | verify_implicit_shutdown () { |
| 192 | r=$1 && |
| 193 | |
| 194 | k=0 && |
| 195 | while test "$k" -lt $IMPLICIT_TIMEOUT |
| 196 | do |
| 197 | git -C $r fsmonitor--daemon status || return 0 |
| 198 | |
| 199 | sleep 1 |
| 200 | k=$(( $k + 1 )) |
| 201 | done && |
| 202 | |
| 203 | return 1 |
| 204 | } |
| 205 | |
| 206 | test_expect_success 'implicit daemon stop (delete .git)' ' |
| 207 | test_when_finished "stop_daemon_delete_repo test_implicit_1" && |
| 208 | |
| 209 | git init test_implicit_1 && |
| 210 | |
| 211 | start_daemon -C test_implicit_1 && |
| 212 | |
| 213 | # deleting the .git directory will implicitly stop the daemon. |
| 214 | rm -rf test_implicit_1/.git && |
| 215 | |
| 216 | # [1] Create an empty .git directory so that the following Git |
| 217 | # command will stay relative to the `-C` directory. |
| 218 | # |
| 219 | # Without this, the Git command will override the requested |
| 220 | # -C argument and crawl out to the containing Git source tree. |
| 221 | # This would make the test result dependent upon whether we |
| 222 | # were using fsmonitor on our development worktree. |
| 223 | # |
| 224 | mkdir test_implicit_1/.git && |
| 225 | |
| 226 | verify_implicit_shutdown test_implicit_1 |
| 227 | ' |
| 228 | |
| 229 | test_expect_success 'implicit daemon stop (rename .git)' ' |
| 230 | test_when_finished "stop_daemon_delete_repo test_implicit_2" && |
| 231 | |
| 232 | git init test_implicit_2 && |
| 233 | |
| 234 | start_daemon -C test_implicit_2 && |
| 235 | |
| 236 | # renaming the .git directory will implicitly stop the daemon. |
| 237 | mv test_implicit_2/.git test_implicit_2/.xxx && |
| 238 | |
| 239 | # See [1] above. |
| 240 | # |
| 241 | mkdir test_implicit_2/.git && |
| 242 | |
| 243 | verify_implicit_shutdown test_implicit_2 |
| 244 | ' |
| 245 | |
| 246 | # File systems on Windows may or may not have shortnames. |
| 247 | # This is a volume-specific setting on modern systems. |
| 248 | # "C:/" drives are required to have them enabled. Other |
| 249 | # hard drives default to disabled. |
| 250 | # |
| 251 | # This is a crude test to see if shortnames are enabled |
| 252 | # on the volume containing the test directory. It is |
| 253 | # crude, but it does not require elevation like `fsutil`. |
| 254 | # |
| 255 | test_lazy_prereq SHORTNAMES ' |
| 256 | mkdir .foo && |
| 257 | test -d "FOO~1" |
| 258 | ' |
| 259 | |
| 260 | # Here we assume that the shortname of ".git" is "GIT~1". |
| 261 | test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~1)' ' |
| 262 | test_when_finished "stop_daemon_delete_repo test_implicit_1s" && |
| 263 | |
| 264 | git init test_implicit_1s && |
| 265 | |
| 266 | start_daemon -C test_implicit_1s && |
| 267 | |
| 268 | # renaming the .git directory will implicitly stop the daemon. |
| 269 | # this moves {.git, GIT~1} to {.gitxyz, GITXYZ~1}. |
| 270 | # the rename-from FS Event will contain the shortname. |
| 271 | # |
| 272 | mv test_implicit_1s/GIT~1 test_implicit_1s/.gitxyz && |
| 273 | |
| 274 | # See [1] above. |
| 275 | # this moves {.gitxyz, GITXYZ~1} to {.git, GIT~1}. |
| 276 | mv test_implicit_1s/.gitxyz test_implicit_1s/.git && |
| 277 | |
| 278 | verify_implicit_shutdown test_implicit_1s |
| 279 | ' |
| 280 | |
| 281 | # Here we first create a file with LONGNAME of "GIT~1" before |
| 282 | # we create the repo. This will cause the shortname of ".git" |
| 283 | # to be "GIT~2". |
| 284 | test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' ' |
| 285 | test_when_finished "stop_daemon_delete_repo test_implicit_1s2" && |
| 286 | |
| 287 | mkdir test_implicit_1s2 && |
| 288 | echo HELLO >test_implicit_1s2/GIT~1 && |
| 289 | git init test_implicit_1s2 && |
| 290 | |
| 291 | test_path_is_file test_implicit_1s2/GIT~1 && |
| 292 | test_path_is_dir test_implicit_1s2/GIT~2 && |
| 293 | |
| 294 | start_daemon -C test_implicit_1s2 && |
| 295 | |
| 296 | # renaming the .git directory will implicitly stop the daemon. |
| 297 | # the rename-from FS Event will contain the shortname. |
| 298 | # |
| 299 | mv test_implicit_1s2/GIT~2 test_implicit_1s2/.gitxyz && |
| 300 | |
| 301 | # See [1] above. |
| 302 | mv test_implicit_1s2/.gitxyz test_implicit_1s2/.git && |
| 303 | |
| 304 | verify_implicit_shutdown test_implicit_1s2 |
| 305 | ' |
| 306 | |
| 307 | test_expect_success 'cannot start multiple daemons' ' |
| 308 | test_when_finished "stop_daemon_delete_repo test_multiple" && |
| 309 | |
| 310 | git init test_multiple && |
| 311 | |
| 312 | start_daemon -C test_multiple && |
| 313 | |
| 314 | test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && |
| 315 | grep "fsmonitor--daemon is already running" actual && |
| 316 | |
| 317 | git -C test_multiple fsmonitor--daemon stop && |
| 318 | test_must_fail git -C test_multiple fsmonitor--daemon status |
| 319 | ' |
| 320 | |
| 321 | # These tests use the main repo in the trash directory |
| 322 | |
| 323 | test_expect_success 'setup' ' |
| 324 | >tracked && |
| 325 | >modified && |
| 326 | >delete && |
| 327 | >rename && |
| 328 | mkdir dir1 && |
| 329 | >dir1/tracked && |
| 330 | >dir1/modified && |
| 331 | >dir1/delete && |
| 332 | >dir1/rename && |
| 333 | mkdir dir2 && |
| 334 | >dir2/tracked && |
| 335 | >dir2/modified && |
| 336 | >dir2/delete && |
| 337 | >dir2/rename && |
| 338 | mkdir dirtorename && |
| 339 | >dirtorename/a && |
| 340 | >dirtorename/b && |
| 341 | |
| 342 | cat >.gitignore <<-\EOF && |
| 343 | .gitignore |
| 344 | expect* |
| 345 | actual* |
| 346 | flush* |
| 347 | trace* |
| 348 | EOF |
| 349 | |
| 350 | mkdir -p T1/T2/T3/T4 && |
| 351 | echo 1 >T1/F1 && |
| 352 | echo 1 >T1/T2/F1 && |
| 353 | echo 1 >T1/T2/T3/F1 && |
| 354 | echo 1 >T1/T2/T3/T4/F1 && |
| 355 | echo 2 >T1/F2 && |
| 356 | echo 2 >T1/T2/F2 && |
| 357 | echo 2 >T1/T2/T3/F2 && |
| 358 | echo 2 >T1/T2/T3/T4/F2 && |
| 359 | |
| 360 | git -c core.fsmonitor=false add . && |
| 361 | test_tick && |
| 362 | git -c core.fsmonitor=false commit -m initial && |
| 363 | |
| 364 | git config core.fsmonitor true |
| 365 | ' |
| 366 | |
| 367 | # The test already explicitly stopped (or tried to stop) the daemon. |
| 368 | # This is here in case something else fails first. |
| 369 | # |
| 370 | redundant_stop_daemon () { |
| 371 | test_might_fail git fsmonitor--daemon stop |
| 372 | } |
| 373 | |
| 374 | test_expect_success 'update-index implicitly starts daemon' ' |
| 375 | test_when_finished redundant_stop_daemon && |
| 376 | |
| 377 | test_must_fail git fsmonitor--daemon status && |
| 378 | |
| 379 | GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \ |
| 380 | git update-index --fsmonitor && |
| 381 | |
| 382 | git fsmonitor--daemon status && |
| 383 | test_might_fail git fsmonitor--daemon stop && |
| 384 | |
| 385 | # Confirm that the trace2 log contains a record of the |
| 386 | # daemon starting. |
| 387 | test_subcommand git fsmonitor--daemon start <.git/trace_implicit_1 |
| 388 | ' |
| 389 | |
| 390 | test_expect_success 'status implicitly starts daemon' ' |
| 391 | test_when_finished redundant_stop_daemon && |
| 392 | |
| 393 | test_must_fail git fsmonitor--daemon status && |
| 394 | |
| 395 | GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \ |
| 396 | git status >actual && |
| 397 | |
| 398 | git fsmonitor--daemon status && |
| 399 | test_might_fail git fsmonitor--daemon stop && |
| 400 | |
| 401 | # Confirm that the trace2 log contains a record of the |
| 402 | # daemon starting. |
| 403 | test_subcommand git fsmonitor--daemon start <.git/trace_implicit_2 |
| 404 | ' |
| 405 | |
| 406 | edit_files () { |
| 407 | echo 1 >modified && |
| 408 | echo 2 >dir1/modified && |
| 409 | echo 3 >dir2/modified && |
| 410 | >dir1/untracked |
| 411 | } |
| 412 | |
| 413 | delete_files () { |
| 414 | rm -f delete && |
| 415 | rm -f dir1/delete && |
| 416 | rm -f dir2/delete |
| 417 | } |
| 418 | |
| 419 | create_files () { |
| 420 | echo 1 >new && |
| 421 | echo 2 >dir1/new && |
| 422 | echo 3 >dir2/new |
| 423 | } |
| 424 | |
| 425 | rename_files () { |
| 426 | mv rename renamed && |
| 427 | mv dir1/rename dir1/renamed && |
| 428 | mv dir2/rename dir2/renamed |
| 429 | } |
| 430 | |
| 431 | file_to_directory () { |
| 432 | rm -f delete && |
| 433 | mkdir delete && |
| 434 | echo 1 >delete/new |
| 435 | } |
| 436 | |
| 437 | directory_to_file () { |
| 438 | rm -rf dir1 && |
| 439 | echo 1 >dir1 |
| 440 | } |
| 441 | |
| 442 | move_directory_contents_deeper() { |
| 443 | mkdir T1/_new_ && |
| 444 | mv T1/[A-Z]* T1/_new_ |
| 445 | } |
| 446 | |
| 447 | move_directory_up() { |
| 448 | mv T1/T2/T3 T1 |
| 449 | } |
| 450 | |
| 451 | move_directory() { |
| 452 | mv T1/T2/T3 T1/T2/NewT3 |
| 453 | } |
| 454 | |
| 455 | # The next few test cases confirm that our fsmonitor daemon sees each type |
| 456 | # of OS filesystem notification that we care about. At this layer we just |
| 457 | # ensure we are getting the OS notifications and do not try to confirm what |
| 458 | # is reported by `git status`. |
| 459 | # |
| 460 | # We use retry_grep to handle races between the daemon writing events |
| 461 | # to the trace file and our check. |
| 462 | # |
| 463 | # We `reset` and `clean` at the bottom of each test (and before stopping the |
| 464 | # daemon) because these commands might implicitly restart the daemon. |
| 465 | |
| 466 | clean_up_repo_and_stop_daemon () { |
| 467 | git reset --hard HEAD && |
| 468 | git clean -fd && |
| 469 | test_might_fail git fsmonitor--daemon stop && |
| 470 | rm -f .git/trace |
| 471 | } |
| 472 | |
| 473 | # Retry a grep up to RETRY_TIMEOUT times until it succeeds. |
| 474 | # |
| 475 | RETRY_TIMEOUT=5 |
| 476 | |
| 477 | retry_grep () { |
| 478 | nr_tries_left=$RETRY_TIMEOUT |
| 479 | until grep "$1" "$2" 2>/dev/null |
| 480 | do |
| 481 | if test $nr_tries_left -eq 0 |
| 482 | then |
| 483 | grep "$1" "$2" |
| 484 | return |
| 485 | fi |
| 486 | nr_tries_left=$(($nr_tries_left - 1)) |
| 487 | sleep 1 |
| 488 | done |
| 489 | } |
| 490 | |
| 491 | test_expect_success 'edit some files' ' |
| 492 | test_when_finished clean_up_repo_and_stop_daemon && |
| 493 | |
| 494 | start_daemon --tf "$PWD/.git/trace" && |
| 495 | |
| 496 | edit_files && |
| 497 | |
| 498 | retry_grep "^event: dir1/modified$" .git/trace && |
| 499 | retry_grep "^event: dir2/modified$" .git/trace && |
| 500 | retry_grep "^event: modified$" .git/trace && |
| 501 | retry_grep "^event: dir1/untracked$" .git/trace |
| 502 | ' |
| 503 | |
| 504 | test_expect_success 'create some files' ' |
| 505 | test_when_finished clean_up_repo_and_stop_daemon && |
| 506 | |
| 507 | start_daemon --tf "$PWD/.git/trace" && |
| 508 | |
| 509 | create_files && |
| 510 | |
| 511 | retry_grep "^event: dir1/new$" .git/trace && |
| 512 | retry_grep "^event: dir2/new$" .git/trace && |
| 513 | retry_grep "^event: new$" .git/trace |
| 514 | ' |
| 515 | |
| 516 | test_expect_success 'delete some files' ' |
| 517 | test_when_finished clean_up_repo_and_stop_daemon && |
| 518 | |
| 519 | start_daemon --tf "$PWD/.git/trace" && |
| 520 | |
| 521 | delete_files && |
| 522 | |
| 523 | retry_grep "^event: dir1/delete$" .git/trace && |
| 524 | retry_grep "^event: dir2/delete$" .git/trace && |
| 525 | retry_grep "^event: delete$" .git/trace |
| 526 | ' |
| 527 | |
| 528 | test_expect_success 'rename some files' ' |
| 529 | test_when_finished clean_up_repo_and_stop_daemon && |
| 530 | |
| 531 | start_daemon --tf "$PWD/.git/trace" && |
| 532 | |
| 533 | rename_files && |
| 534 | |
| 535 | retry_grep "^event: dir1/rename$" .git/trace && |
| 536 | retry_grep "^event: dir2/rename$" .git/trace && |
| 537 | retry_grep "^event: rename$" .git/trace && |
| 538 | retry_grep "^event: dir1/renamed$" .git/trace && |
| 539 | retry_grep "^event: dir2/renamed$" .git/trace && |
| 540 | retry_grep "^event: renamed$" .git/trace |
| 541 | ' |
| 542 | |
| 543 | test_expect_success 'rename directory' ' |
| 544 | test_when_finished clean_up_repo_and_stop_daemon && |
| 545 | |
| 546 | start_daemon --tf "$PWD/.git/trace" && |
| 547 | |
| 548 | mv dirtorename dirrenamed && |
| 549 | |
| 550 | retry_grep "^event: dirtorename/*$" .git/trace && |
| 551 | retry_grep "^event: dirrenamed/*$" .git/trace |
| 552 | ' |
| 553 | |
| 554 | test_expect_success 'file changes to directory' ' |
| 555 | test_when_finished clean_up_repo_and_stop_daemon && |
| 556 | |
| 557 | start_daemon --tf "$PWD/.git/trace" && |
| 558 | |
| 559 | file_to_directory && |
| 560 | |
| 561 | retry_grep "^event: delete$" .git/trace && |
| 562 | retry_grep "^event: delete/new$" .git/trace |
| 563 | ' |
| 564 | |
| 565 | test_expect_success 'directory changes to a file' ' |
| 566 | test_when_finished clean_up_repo_and_stop_daemon && |
| 567 | |
| 568 | start_daemon --tf "$PWD/.git/trace" && |
| 569 | |
| 570 | directory_to_file && |
| 571 | |
| 572 | retry_grep "^event: dir1$" .git/trace |
| 573 | ' |
| 574 | |
| 575 | test_expect_success 'rapid nested directory creation' ' |
| 576 | test_when_finished "git fsmonitor--daemon stop; rm -rf rapid" && |
| 577 | |
| 578 | start_daemon --tf "$PWD/.git/trace" && |
| 579 | |
| 580 | # Rapidly create nested directories to exercise race conditions |
| 581 | # where directory watches may be added concurrently during |
| 582 | # event processing and recursive scanning. |
| 583 | for i in $(test_seq 1 20) |
| 584 | do |
| 585 | mkdir -p "rapid/nested/dir$i/subdir/deep" || return 1 |
| 586 | done && |
| 587 | |
| 588 | # Give the daemon time to process all events |
| 589 | sleep 1 && |
| 590 | |
| 591 | test-tool fsmonitor-client query --token 0 && |
| 592 | |
| 593 | # Verify daemon is still running (did not crash) |
| 594 | git fsmonitor--daemon status |
| 595 | ' |
| 596 | |
| 597 | # The next few test cases exercise the token-resync code. When filesystem |
| 598 | # drops events (because of filesystem velocity or because the daemon isn't |
| 599 | # polling fast enough), we need to discard the cached data (relative to the |
| 600 | # current token) and start collecting events under a new token. |
| 601 | # |
| 602 | # the 'test-tool fsmonitor-client flush' command can be used to send a |
| 603 | # "flush" message to a running daemon and ask it to do a flush/resync. |
| 604 | |
| 605 | test_expect_success 'flush cached data' ' |
| 606 | test_when_finished "stop_daemon_delete_repo test_flush" && |
| 607 | |
| 608 | git init test_flush && |
| 609 | |
| 610 | start_daemon -C test_flush --tf "$PWD/.git/trace_daemon" --tk true && |
| 611 | |
| 612 | # The daemon should have an initial token with no events in _0 and |
| 613 | # then a few (probably platform-specific number of) events in _1. |
| 614 | # These should both have the same <token_id>. |
| 615 | |
| 616 | test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 && |
| 617 | nul_to_q <actual_0 >actual_q0 && |
| 618 | |
| 619 | >test_flush/file_1 && |
| 620 | >test_flush/file_2 && |
| 621 | |
| 622 | test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && |
| 623 | nul_to_q <actual_1 >actual_q1 && |
| 624 | |
| 625 | grep "file_1" actual_q1 && |
| 626 | |
| 627 | # Force a flush. This will change the <token_id>, reset the <seq_nr>, and |
| 628 | # flush the file data. Then create some events and ensure that the file |
| 629 | # again appears in the cache. It should have the new <token_id>. |
| 630 | |
| 631 | test-tool -C test_flush fsmonitor-client flush >flush_0 && |
| 632 | nul_to_q <flush_0 >flush_q0 && |
| 633 | grep "^builtin:test_00000002:0Q/Q$" flush_q0 && |
| 634 | |
| 635 | test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 && |
| 636 | nul_to_q <actual_2 >actual_q2 && |
| 637 | |
| 638 | grep "^builtin:test_00000002:0Q$" actual_q2 && |
| 639 | |
| 640 | >test_flush/file_3 && |
| 641 | |
| 642 | test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 && |
| 643 | nul_to_q <actual_3 >actual_q3 && |
| 644 | |
| 645 | grep "file_3" actual_q3 |
| 646 | ' |
| 647 | |
| 648 | # The next few test cases create repos where the .git directory is NOT |
| 649 | # inside the one of the working directory. That is, where .git is a file |
| 650 | # that points to a directory elsewhere. This happens for submodules and |
| 651 | # non-primary worktrees. |
| 652 | |
| 653 | test_expect_success 'setup worktree base' ' |
| 654 | git init wt-base && |
| 655 | echo 1 >wt-base/file1 && |
| 656 | git -C wt-base add file1 && |
| 657 | git -C wt-base commit -m "c1" |
| 658 | ' |
| 659 | |
| 660 | test_expect_success 'worktree with .git file' ' |
| 661 | git -C wt-base worktree add ../wt-secondary && |
| 662 | |
| 663 | start_daemon -C wt-secondary \ |
| 664 | --tf "$PWD/trace_wt_secondary" \ |
| 665 | --t2 "$PWD/trace2_wt_secondary" && |
| 666 | |
| 667 | git -C wt-secondary fsmonitor--daemon stop && |
| 668 | test_must_fail git -C wt-secondary fsmonitor--daemon status |
| 669 | ' |
| 670 | |
| 671 | # NEEDSWORK: Repeat one of the "edit" tests on wt-secondary and |
| 672 | # confirm that we get the same events and behavior -- that is, that |
| 673 | # fsmonitor--daemon correctly watches BOTH the working directory and |
| 674 | # the external GITDIR directory and behaves the same as when ".git" |
| 675 | # is a directory inside the working directory. |
| 676 | |
| 677 | test_expect_success 'cleanup worktrees' ' |
| 678 | stop_daemon_delete_repo wt-secondary && |
| 679 | stop_daemon_delete_repo wt-base |
| 680 | ' |
| 681 | |
| 682 | # The next few tests perform arbitrary/contrived file operations and |
| 683 | # confirm that status is correct. That is, that the data (or lack of |
| 684 | # data) from fsmonitor doesn't cause incorrect results. And doesn't |
| 685 | # cause incorrect results when the untracked-cache is enabled. |
| 686 | |
| 687 | test_lazy_prereq UNTRACKED_CACHE ' |
| 688 | git update-index --test-untracked-cache |
| 689 | ' |
| 690 | |
| 691 | test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' ' |
| 692 | test_unconfig core.fsmonitor && |
| 693 | git update-index --no-fsmonitor && |
| 694 | test_might_fail git fsmonitor--daemon stop |
| 695 | ' |
| 696 | |
| 697 | matrix_clean_up_repo () { |
| 698 | git reset --hard HEAD && |
| 699 | git clean -fd |
| 700 | } |
| 701 | |
| 702 | matrix_try () { |
| 703 | uc=$1 && |
| 704 | fsm=$2 && |
| 705 | fn=$3 && |
| 706 | |
| 707 | if test $uc = true && test $fsm = false |
| 708 | then |
| 709 | # The untracked-cache is buggy when FSMonitor is |
| 710 | # DISABLED, so skip the tests for this matrix |
| 711 | # combination. |
| 712 | # |
| 713 | # We've observed random, occasional test failures on |
| 714 | # Windows and MacOS when the UC is turned on and FSM |
| 715 | # is turned off. These are rare, but they do happen |
| 716 | # indicating that it is probably a race condition within |
| 717 | # the untracked cache itself. |
| 718 | # |
| 719 | # It usually happens when a test does F/D trickery and |
| 720 | # then the NEXT test fails because of extra status |
| 721 | # output from stale UC data from the previous test. |
| 722 | # |
| 723 | # Since FSMonitor is not involved in the error, skip |
| 724 | # the tests for this matrix combination. |
| 725 | # |
| 726 | return 0 |
| 727 | fi && |
| 728 | |
| 729 | test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' |
| 730 | matrix_clean_up_repo && |
| 731 | $fn && |
| 732 | if test $uc = false && test $fsm = false |
| 733 | then |
| 734 | git status --porcelain=v1 >.git/expect.$fn |
| 735 | else |
| 736 | git status --porcelain=v1 >.git/actual.$fn && |
| 737 | test_cmp .git/expect.$fn .git/actual.$fn |
| 738 | fi |
| 739 | ' |
| 740 | } |
| 741 | |
| 742 | uc_values="false" |
| 743 | test_have_prereq UNTRACKED_CACHE && uc_values="false true" |
| 744 | for uc_val in $uc_values |
| 745 | do |
| 746 | if test $uc_val = false |
| 747 | then |
| 748 | test_expect_success "Matrix[uc:$uc_val] disable untracked cache" ' |
| 749 | git config core.untrackedcache false && |
| 750 | git update-index --no-untracked-cache |
| 751 | ' |
| 752 | else |
| 753 | test_expect_success "Matrix[uc:$uc_val] enable untracked cache" ' |
| 754 | git config core.untrackedcache true && |
| 755 | git update-index --untracked-cache |
| 756 | ' |
| 757 | fi |
| 758 | |
| 759 | fsm_values="false true" |
| 760 | for fsm_val in $fsm_values |
| 761 | do |
| 762 | if test $fsm_val = false |
| 763 | then |
| 764 | test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor" ' |
| 765 | test_unconfig core.fsmonitor && |
| 766 | git update-index --no-fsmonitor && |
| 767 | test_might_fail git fsmonitor--daemon stop |
| 768 | ' |
| 769 | else |
| 770 | test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] enable fsmonitor" ' |
| 771 | git config core.fsmonitor true && |
| 772 | git fsmonitor--daemon start && |
| 773 | git update-index --fsmonitor |
| 774 | ' |
| 775 | fi |
| 776 | |
| 777 | matrix_try $uc_val $fsm_val edit_files |
| 778 | matrix_try $uc_val $fsm_val delete_files |
| 779 | matrix_try $uc_val $fsm_val create_files |
| 780 | matrix_try $uc_val $fsm_val rename_files |
| 781 | matrix_try $uc_val $fsm_val file_to_directory |
| 782 | matrix_try $uc_val $fsm_val directory_to_file |
| 783 | |
| 784 | matrix_try $uc_val $fsm_val move_directory_contents_deeper |
| 785 | matrix_try $uc_val $fsm_val move_directory_up |
| 786 | matrix_try $uc_val $fsm_val move_directory |
| 787 | |
| 788 | if test $fsm_val = true |
| 789 | then |
| 790 | test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" ' |
| 791 | test_unconfig core.fsmonitor && |
| 792 | git update-index --no-fsmonitor && |
| 793 | test_might_fail git fsmonitor--daemon stop |
| 794 | ' |
| 795 | fi |
| 796 | done |
| 797 | done |
| 798 | |
| 799 | # Test Unicode UTF-8 characters in the pathname of the working |
| 800 | # directory root. Use of "*A()" routines rather than "*W()" routines |
| 801 | # on Windows can sometimes lead to odd failures. |
| 802 | # |
| 803 | u1=$(printf "u_c3_a6__\xC3\xA6") |
| 804 | u2=$(printf "u_e2_99_ab__\xE2\x99\xAB") |
| 805 | u_values="$u1 $u2" |
| 806 | for u in $u_values |
| 807 | do |
| 808 | test_expect_success "unicode in repo root path: $u" ' |
| 809 | test_when_finished "stop_daemon_delete_repo $u" && |
| 810 | |
| 811 | git init "$u" && |
| 812 | echo 1 >"$u"/file1 && |
| 813 | git -C "$u" add file1 && |
| 814 | git -C "$u" config core.fsmonitor true && |
| 815 | |
| 816 | start_daemon -C "$u" && |
| 817 | git -C "$u" status >actual && |
| 818 | grep "new file: file1" actual |
| 819 | ' |
| 820 | done |
| 821 | |
| 822 | # Test fsmonitor interaction with submodules. |
| 823 | # |
| 824 | # If we start the daemon in the super, it will see FS events for |
| 825 | # everything in the working directory cone and this includes any |
| 826 | # files/directories contained *within* the submodules. |
| 827 | # |
| 828 | # A `git status` at top level will get events for items within the |
| 829 | # submodule and ignore them, since they aren't named in the index |
| 830 | # of the super repo. This makes the fsmonitor response a little |
| 831 | # noisy, but it doesn't alter the correctness of the state of the |
| 832 | # super-proper. |
| 833 | # |
| 834 | # When we have submodules, `git status` normally does a recursive |
| 835 | # status on each of the submodules and adds a summary row for any |
| 836 | # dirty submodules. (See the "S..." bits in porcelain V2 output.) |
| 837 | # |
| 838 | # It is therefore important that the top level status not be tricked |
| 839 | # by the FSMonitor response to skip those recursive calls. That is, |
| 840 | # even if FSMonitor says that the mtime of the submodule directory |
| 841 | # hasn't changed and it could be implicitly marked valid, we must |
| 842 | # not take that shortcut. We need to force the recursion into the |
| 843 | # submodule so that we get a summary of the status *within* the |
| 844 | # submodule. |
| 845 | |
| 846 | create_super () { |
| 847 | super="$1" && |
| 848 | |
| 849 | git init "$super" && |
| 850 | echo x >"$super/file_1" && |
| 851 | echo y >"$super/file_2" && |
| 852 | echo z >"$super/file_3" && |
| 853 | mkdir "$super/dir_1" && |
| 854 | echo a >"$super/dir_1/file_11" && |
| 855 | echo b >"$super/dir_1/file_12" && |
| 856 | mkdir "$super/dir_1/dir_2" && |
| 857 | echo a >"$super/dir_1/dir_2/file_21" && |
| 858 | echo b >"$super/dir_1/dir_2/file_22" && |
| 859 | git -C "$super" add . && |
| 860 | git -C "$super" commit -m "initial $super commit" |
| 861 | } |
| 862 | |
| 863 | create_sub () { |
| 864 | sub="$1" && |
| 865 | |
| 866 | git init "$sub" && |
| 867 | echo x >"$sub/file_x" && |
| 868 | echo y >"$sub/file_y" && |
| 869 | echo z >"$sub/file_z" && |
| 870 | mkdir "$sub/dir_x" && |
| 871 | echo a >"$sub/dir_x/file_a" && |
| 872 | echo b >"$sub/dir_x/file_b" && |
| 873 | mkdir "$sub/dir_x/dir_y" && |
| 874 | echo a >"$sub/dir_x/dir_y/file_a" && |
| 875 | echo b >"$sub/dir_x/dir_y/file_b" && |
| 876 | git -C "$sub" add . && |
| 877 | git -C "$sub" commit -m "initial $sub commit" |
| 878 | } |
| 879 | |
| 880 | my_match_and_clean () { |
| 881 | git -C super --no-optional-locks status --porcelain=v2 >actual.with && |
| 882 | git -C super --no-optional-locks -c core.fsmonitor=false \ |
| 883 | status --porcelain=v2 >actual.without && |
| 884 | test_cmp actual.with actual.without && |
| 885 | |
| 886 | git -C super --no-optional-locks diff-index --name-status HEAD >actual.with && |
| 887 | git -C super --no-optional-locks -c core.fsmonitor=false \ |
| 888 | diff-index --name-status HEAD >actual.without && |
| 889 | test_cmp actual.with actual.without && |
| 890 | |
| 891 | git -C super/dir_1/dir_2/sub reset --hard && |
| 892 | git -C super/dir_1/dir_2/sub clean -d -f |
| 893 | } |
| 894 | |
| 895 | test_expect_success 'submodule setup' ' |
| 896 | git config --global protocol.file.allow always |
| 897 | ' |
| 898 | |
| 899 | test_expect_success 'submodule always visited' ' |
| 900 | test_when_finished "git -C super fsmonitor--daemon stop; \ |
| 901 | rm -rf super; \ |
| 902 | rm -rf sub" && |
| 903 | |
| 904 | create_super super && |
| 905 | create_sub sub && |
| 906 | |
| 907 | git -C super submodule add ../sub ./dir_1/dir_2/sub && |
| 908 | git -C super commit -m "add sub" && |
| 909 | |
| 910 | start_daemon -C super && |
| 911 | git -C super config core.fsmonitor true && |
| 912 | git -C super update-index --fsmonitor && |
| 913 | git -C super status && |
| 914 | |
| 915 | # Now run pairs of commands w/ and w/o FSMonitor while we make |
| 916 | # some dirt in the submodule and confirm matching output. |
| 917 | |
| 918 | # Completely clean status. |
| 919 | my_match_and_clean && |
| 920 | |
| 921 | # .M S..U |
| 922 | echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_u && |
| 923 | my_match_and_clean && |
| 924 | |
| 925 | # .M S.M. |
| 926 | echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_m && |
| 927 | git -C super/dir_1/dir_2/sub add . && |
| 928 | my_match_and_clean && |
| 929 | |
| 930 | # .M S.M. |
| 931 | echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && |
| 932 | git -C super/dir_1/dir_2/sub add . && |
| 933 | my_match_and_clean && |
| 934 | |
| 935 | # .M SC.. |
| 936 | echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && |
| 937 | git -C super/dir_1/dir_2/sub add . && |
| 938 | git -C super/dir_1/dir_2/sub commit -m "SC.." && |
| 939 | my_match_and_clean |
| 940 | ' |
| 941 | |
| 942 | # If a submodule has a `sub/.git/` directory (rather than a file |
| 943 | # pointing to the super's `.git/modules/sub`) and `core.fsmonitor` |
| 944 | # turned on in the submodule and the daemon is not yet started in |
| 945 | # the submodule, and someone does a `git submodule absorbgitdirs` |
| 946 | # in the super, Git will recursively invoke `git submodule--helper` |
| 947 | # to do the work and this may try to read the index. This will |
| 948 | # try to start the daemon in the submodule. |
| 949 | |
| 950 | test_expect_success "submodule absorbgitdirs implicitly starts daemon" ' |
| 951 | test_when_finished "rm -rf super; \ |
| 952 | rm -rf sub; \ |
| 953 | rm super-sub.trace" && |
| 954 | |
| 955 | create_super super && |
| 956 | create_sub sub && |
| 957 | |
| 958 | # Copy rather than submodule add so that we get a .git dir. |
| 959 | cp -R ./sub ./super/dir_1/dir_2/sub && |
| 960 | |
| 961 | git -C super/dir_1/dir_2/sub config core.fsmonitor true && |
| 962 | |
| 963 | git -C super submodule add ../sub ./dir_1/dir_2/sub && |
| 964 | git -C super commit -m "add sub" && |
| 965 | |
| 966 | test_path_is_dir super/dir_1/dir_2/sub/.git && |
| 967 | |
| 968 | cwd="$(cd super && pwd)" && |
| 969 | cat >expect <<-EOF && |
| 970 | Migrating git directory of '\''dir_1/dir_2/sub'\'' from |
| 971 | '\''$cwd/dir_1/dir_2/sub/.git'\'' to |
| 972 | '\''$cwd/.git/modules/dir_1/dir_2/sub'\'' |
| 973 | EOF |
| 974 | GIT_TRACE2_EVENT="$PWD/super-sub.trace" \ |
| 975 | git -C super submodule absorbgitdirs >out 2>actual && |
| 976 | test_cmp expect actual && |
| 977 | test_must_be_empty out && |
| 978 | |
| 979 | # Confirm that the trace2 log contains a record of the |
| 980 | # daemon starting. |
| 981 | test_subcommand git fsmonitor--daemon start <super-sub.trace |
| 982 | ' |
| 983 | |
| 984 | start_git_in_background () { |
| 985 | git "$@" & |
| 986 | git_pid=$! |
| 987 | git_pgid=$(ps -o pgid= -p $git_pid 2>/dev/null || |
| 988 | awk '{print $5}' /proc/$git_pid/stat 2>/dev/null) && |
| 989 | git_pgid="${git_pgid## }" && |
| 990 | git_pgid="${git_pgid%% }" |
| 991 | nr_tries_left=10 |
| 992 | while true |
| 993 | do |
| 994 | if test $nr_tries_left -eq 0 |
| 995 | then |
| 996 | kill -- -$git_pgid |
| 997 | exit 1 |
| 998 | fi |
| 999 | sleep 1 |
| 1000 | nr_tries_left=$(($nr_tries_left - 1)) |
| 1001 | done >/dev/null 2>&1 3>&- 4>&- 5>&- 6>&- 7>&- & |
| 1002 | watchdog_pid=$! |
| 1003 | wait $git_pid |
| 1004 | } |
| 1005 | |
| 1006 | stop_git () { |
| 1007 | test -n "$git_pgid" || return 0 |
| 1008 | while kill -0 -- -$git_pgid 2>/dev/null |
| 1009 | do |
| 1010 | kill -- -$git_pgid 2>/dev/null |
| 1011 | sleep 1 |
| 1012 | done |
| 1013 | } |
| 1014 | |
| 1015 | stop_watchdog () { |
| 1016 | while kill -0 $watchdog_pid |
| 1017 | do |
| 1018 | kill $watchdog_pid |
| 1019 | sleep 1 |
| 1020 | done |
| 1021 | } |
| 1022 | |
| 1023 | test_expect_success !MINGW "submodule implicitly starts daemon by pull" ' |
| 1024 | test_atexit "stop_watchdog" && |
| 1025 | test_when_finished "set +m; stop_git; rm -rf cloned super sub" && |
| 1026 | |
| 1027 | create_super super && |
| 1028 | create_sub sub && |
| 1029 | |
| 1030 | git -C super submodule add ../sub ./dir_1/dir_2/sub && |
| 1031 | git -C super commit -m "add sub" && |
| 1032 | git clone --recurse-submodules super cloned && |
| 1033 | |
| 1034 | git -C cloned/dir_1/dir_2/sub config core.fsmonitor true && |
| 1035 | set -m && |
| 1036 | start_git_in_background -C cloned pull --recurse-submodules |
| 1037 | ' |
| 1038 | |
| 1039 | # On a case-insensitive file system, confirm that the daemon |
| 1040 | # notices when the .git directory is moved/renamed/deleted |
| 1041 | # regardless of how it is spelled in the FS event. |
| 1042 | # That is, does the FS event receive the spelling of the |
| 1043 | # operation or does it receive the spelling preserved with |
| 1044 | # the file/directory. |
| 1045 | # |
| 1046 | test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' |
| 1047 | test_when_finished "stop_daemon_delete_repo test_insensitive" && |
| 1048 | |
| 1049 | git init test_insensitive && |
| 1050 | |
| 1051 | start_daemon -C test_insensitive --tf "$PWD/insensitive.trace" && |
| 1052 | |
| 1053 | mkdir -p test_insensitive/abc/def && |
| 1054 | echo xyz >test_insensitive/ABC/DEF/xyz && |
| 1055 | |
| 1056 | test_path_is_dir test_insensitive/.git && |
| 1057 | test_path_is_dir test_insensitive/.GIT && |
| 1058 | |
| 1059 | # Rename .git using an alternate spelling to verify that |
| 1060 | # the daemon detects it and automatically shuts down. |
| 1061 | mv test_insensitive/.GIT test_insensitive/.FOO && |
| 1062 | |
| 1063 | # See [1] above. |
| 1064 | mv test_insensitive/.FOO test_insensitive/.git && |
| 1065 | |
| 1066 | verify_implicit_shutdown test_insensitive && |
| 1067 | |
| 1068 | # Verify that events were reported using on-disk spellings of the |
| 1069 | # directories and files that we touched. We may or may not get a |
| 1070 | # trailing slash on modified directories. |
| 1071 | # |
| 1072 | grep -E "^event: abc/?$" ./insensitive.trace && |
| 1073 | grep -E "^event: abc/def/?$" ./insensitive.trace && |
| 1074 | grep -E "^event: abc/def/xyz$" ./insensitive.trace |
| 1075 | ' |
| 1076 | |
| 1077 | # The variable "unicode_debug" is defined in the following library |
| 1078 | # script to dump information about how the (OS, FS) handles Unicode |
| 1079 | # composition. Uncomment the following line if you want to enable it. |
| 1080 | # |
| 1081 | # unicode_debug=true |
| 1082 | |
| 1083 | . "$TEST_DIRECTORY/lib-unicode-nfc-nfd.sh" |
| 1084 | |
| 1085 | # See if the OS or filesystem does NFC/NFD aliasing/munging. |
| 1086 | # |
| 1087 | # The daemon should err on the side of caution and send BOTH the |
| 1088 | # NFC and NFD forms. It does not know the original spelling of |
| 1089 | # the pathname (how the user thinks it should be spelled), so |
| 1090 | # emit both and let the client decide (when necessary). This is |
| 1091 | # similar to "core.precomposeUnicode". |
| 1092 | # |
| 1093 | test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' |
| 1094 | test_when_finished "stop_daemon_delete_repo test_unicode" && |
| 1095 | |
| 1096 | git init test_unicode && |
| 1097 | |
| 1098 | start_daemon -C test_unicode --tf "$PWD/unicode.trace" && |
| 1099 | |
| 1100 | # Create a directory using an NFC spelling. |
| 1101 | # |
| 1102 | mkdir test_unicode/nfc && |
| 1103 | mkdir test_unicode/nfc/c_${utf8_nfc} && |
| 1104 | |
| 1105 | # Create a directory using an NFD spelling. |
| 1106 | # |
| 1107 | mkdir test_unicode/nfd && |
| 1108 | mkdir test_unicode/nfd/d_${utf8_nfd} && |
| 1109 | |
| 1110 | test-tool -C test_unicode fsmonitor-client query --token 0 && |
| 1111 | |
| 1112 | if test_have_prereq UNICODE_NFC_PRESERVED |
| 1113 | then |
| 1114 | # We should have seen NFC event from OS. |
| 1115 | # We should not have synthesized an NFD event. |
| 1116 | grep -E "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && |
| 1117 | grep -E -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace |
| 1118 | else |
| 1119 | # We should have seen NFD event from OS. |
| 1120 | # We should have synthesized an NFC event. |
| 1121 | grep -E "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && |
| 1122 | grep -E "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace |
| 1123 | fi && |
| 1124 | |
| 1125 | # We assume UNICODE_NFD_PRESERVED. |
| 1126 | # We should have seen explicit NFD from OS. |
| 1127 | # We should have synthesized an NFC event. |
| 1128 | grep -E "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && |
| 1129 | grep -E "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace |
| 1130 | ' |
| 1131 | |
| 1132 | test_expect_success 'split-index and FSMonitor work well together' ' |
| 1133 | git init split-index && |
| 1134 | test_when_finished "git -C \"$PWD/split-index\" \ |
| 1135 | fsmonitor--daemon stop" && |
| 1136 | ( |
| 1137 | cd split-index && |
| 1138 | git config core.splitIndex true && |
| 1139 | # force split-index in most cases |
| 1140 | git config splitIndex.maxPercentChange 99 && |
| 1141 | git config core.fsmonitor true && |
| 1142 | |
| 1143 | # Create the following commit topology: |
| 1144 | # |
| 1145 | # * merge three |
| 1146 | # |\ |
| 1147 | # | * three |
| 1148 | # * | merge two |
| 1149 | # |\| |
| 1150 | # | * two |
| 1151 | # * | one |
| 1152 | # |/ |
| 1153 | # * 5a5efd7 initial |
| 1154 | |
| 1155 | test_commit initial && |
| 1156 | test_commit two && |
| 1157 | test_commit three && |
| 1158 | git reset --hard initial && |
| 1159 | test_commit one && |
| 1160 | test_tick && |
| 1161 | git merge two && |
| 1162 | test_tick && |
| 1163 | git merge three && |
| 1164 | |
| 1165 | git rebase --force-rebase -r one |
| 1166 | ) |
| 1167 | ' |
| 1168 | |
| 1169 | # The FSMonitor daemon reports the OBSERVED pathname of modified files |
| 1170 | # and thus contains the OBSERVED spelling on case-insensitive file |
| 1171 | # systems. The daemon does not (and should not) load the .git/index |
| 1172 | # file and therefore does not know the expected case-spelling. Since |
| 1173 | # it is possible for the user to create files/subdirectories with the |
| 1174 | # incorrect case, a modified file event for a tracked will not have |
| 1175 | # the EXPECTED case. This can cause `index_name_pos()` to incorrectly |
| 1176 | # report that the file is untracked. This causes the client to fail to |
| 1177 | # mark the file as possibly dirty (keeping the CE_FSMONITOR_VALID bit |
| 1178 | # set) so that `git status` will avoid inspecting it and thus not |
| 1179 | # present in the status output. |
| 1180 | # |
| 1181 | # The setup is a little contrived. |
| 1182 | # |
| 1183 | test_expect_success CASE_INSENSITIVE_FS 'fsmonitor subdir case wrong on disk' ' |
| 1184 | test_when_finished "stop_daemon_delete_repo subdir_case_wrong" && |
| 1185 | |
| 1186 | git init subdir_case_wrong && |
| 1187 | ( |
| 1188 | cd subdir_case_wrong && |
| 1189 | echo x >AAA && |
| 1190 | echo x >BBB && |
| 1191 | |
| 1192 | mkdir dir1 && |
| 1193 | echo x >dir1/file1 && |
| 1194 | mkdir dir1/dir2 && |
| 1195 | echo x >dir1/dir2/file2 && |
| 1196 | mkdir dir1/dir2/dir3 && |
| 1197 | echo x >dir1/dir2/dir3/file3 && |
| 1198 | |
| 1199 | echo x >yyy && |
| 1200 | echo x >zzz && |
| 1201 | git add . && |
| 1202 | git commit -m "data" && |
| 1203 | |
| 1204 | # This will cause "dir1/" and everything under it |
| 1205 | # to be deleted. |
| 1206 | git sparse-checkout set --cone --sparse-index && |
| 1207 | |
| 1208 | # Create dir2 with the wrong case and then let Git |
| 1209 | # repopulate dir3 -- it will not correct the spelling |
| 1210 | # of dir2. |
| 1211 | mkdir dir1 && |
| 1212 | mkdir dir1/DIR2 && |
| 1213 | git sparse-checkout add dir1/dir2/dir3 |
| 1214 | ) && |
| 1215 | |
| 1216 | start_daemon -C subdir_case_wrong --tf "$PWD/subdir_case_wrong.trace" && |
| 1217 | |
| 1218 | # Enable FSMonitor in the client. Run enough commands for |
| 1219 | # the .git/index to sync up with the daemon with everything |
| 1220 | # marked clean. |
| 1221 | git -C subdir_case_wrong config core.fsmonitor true && |
| 1222 | git -C subdir_case_wrong update-index --fsmonitor && |
| 1223 | git -C subdir_case_wrong status && |
| 1224 | |
| 1225 | # Make some files dirty so that FSMonitor gets FSEvents for |
| 1226 | # each of them. |
| 1227 | echo xx >>subdir_case_wrong/AAA && |
| 1228 | echo xx >>subdir_case_wrong/dir1/DIR2/dir3/file3 && |
| 1229 | echo xx >>subdir_case_wrong/zzz && |
| 1230 | |
| 1231 | GIT_TRACE_FSMONITOR="$PWD/subdir_case_wrong.log" \ |
| 1232 | git -C subdir_case_wrong --no-optional-locks status --short \ |
| 1233 | >"$PWD/subdir_case_wrong.out" && |
| 1234 | |
| 1235 | # "git status" should have gotten file events for each of |
| 1236 | # the 3 files. |
| 1237 | # |
| 1238 | # "dir2" should be in the observed case on disk. |
| 1239 | grep "fsmonitor_refresh_callback" \ |
| 1240 | <"$PWD/subdir_case_wrong.log" \ |
| 1241 | >"$PWD/subdir_case_wrong.log1" && |
| 1242 | |
| 1243 | grep -q "AAA.*pos 0" "$PWD/subdir_case_wrong.log1" && |
| 1244 | grep -q "zzz.*pos 6" "$PWD/subdir_case_wrong.log1" && |
| 1245 | |
| 1246 | grep -q "dir1/DIR2/dir3/file3.*pos -3" "$PWD/subdir_case_wrong.log1" && |
| 1247 | |
| 1248 | # Verify that we get a mapping event to correct the case. |
| 1249 | grep -q "MAP:.*dir1/DIR2/dir3/file3.*dir1/dir2/dir3/file3" \ |
| 1250 | "$PWD/subdir_case_wrong.log1" && |
| 1251 | |
| 1252 | # The refresh-callbacks should have caused "git status" to clear |
| 1253 | # the CE_FSMONITOR_VALID bit on each of those files and caused |
| 1254 | # the worktree scan to visit them and mark them as modified. |
| 1255 | grep -q " M AAA" "$PWD/subdir_case_wrong.out" && |
| 1256 | grep -q " M zzz" "$PWD/subdir_case_wrong.out" && |
| 1257 | grep -q " M dir1/dir2/dir3/file3" "$PWD/subdir_case_wrong.out" |
| 1258 | ' |
| 1259 | |
| 1260 | test_expect_success CASE_INSENSITIVE_FS 'fsmonitor file case wrong on disk' ' |
| 1261 | test_when_finished "stop_daemon_delete_repo file_case_wrong" && |
| 1262 | |
| 1263 | git init file_case_wrong && |
| 1264 | ( |
| 1265 | cd file_case_wrong && |
| 1266 | echo x >AAA && |
| 1267 | echo x >BBB && |
| 1268 | |
| 1269 | mkdir dir1 && |
| 1270 | mkdir dir1/dir2 && |
| 1271 | mkdir dir1/dir2/dir3 && |
| 1272 | echo x >dir1/dir2/dir3/FILE-3-B && |
| 1273 | echo x >dir1/dir2/dir3/XXXX-3-X && |
| 1274 | echo x >dir1/dir2/dir3/file-3-a && |
| 1275 | echo x >dir1/dir2/dir3/yyyy-3-y && |
| 1276 | mkdir dir1/dir2/dir4 && |
| 1277 | echo x >dir1/dir2/dir4/FILE-4-A && |
| 1278 | echo x >dir1/dir2/dir4/XXXX-4-X && |
| 1279 | echo x >dir1/dir2/dir4/file-4-b && |
| 1280 | echo x >dir1/dir2/dir4/yyyy-4-y && |
| 1281 | |
| 1282 | echo x >yyy && |
| 1283 | echo x >zzz && |
| 1284 | git add . && |
| 1285 | git commit -m "data" |
| 1286 | ) && |
| 1287 | |
| 1288 | start_daemon -C file_case_wrong --tf "$PWD/file_case_wrong.trace" && |
| 1289 | |
| 1290 | # Enable FSMonitor in the client. Run enough commands for |
| 1291 | # the .git/index to sync up with the daemon with everything |
| 1292 | # marked clean. |
| 1293 | git -C file_case_wrong config core.fsmonitor true && |
| 1294 | git -C file_case_wrong update-index --fsmonitor && |
| 1295 | git -C file_case_wrong status && |
| 1296 | |
| 1297 | # Make some files dirty so that FSMonitor gets FSEvents for |
| 1298 | # each of them. |
| 1299 | echo xx >>file_case_wrong/AAA && |
| 1300 | echo xx >>file_case_wrong/zzz && |
| 1301 | |
| 1302 | # Rename some files so that FSMonitor sees a create and delete |
| 1303 | # FSEvent for each. (A simple "mv foo FOO" is not portable |
| 1304 | # between macOS and Windows. It works on both platforms, but makes |
| 1305 | # the test messy, since (1) one platform updates "ctime" on the |
| 1306 | # moved file and one does not and (2) it causes a directory event |
| 1307 | # on one platform and not on the other which causes additional |
| 1308 | # scanning during "git status" which causes a "H" vs "h" discrepancy |
| 1309 | # in "git ls-files -f".) So old-school it and move it out of the |
| 1310 | # way and copy it to the case-incorrect name so that we get fresh |
| 1311 | # "ctime" and "mtime" values. |
| 1312 | |
| 1313 | mv file_case_wrong/dir1/dir2/dir3/file-3-a file_case_wrong/dir1/dir2/dir3/ORIG && |
| 1314 | cp file_case_wrong/dir1/dir2/dir3/ORIG file_case_wrong/dir1/dir2/dir3/FILE-3-A && |
| 1315 | rm file_case_wrong/dir1/dir2/dir3/ORIG && |
| 1316 | mv file_case_wrong/dir1/dir2/dir4/FILE-4-A file_case_wrong/dir1/dir2/dir4/ORIG && |
| 1317 | cp file_case_wrong/dir1/dir2/dir4/ORIG file_case_wrong/dir1/dir2/dir4/file-4-a && |
| 1318 | rm file_case_wrong/dir1/dir2/dir4/ORIG && |
| 1319 | |
| 1320 | # Run status enough times to fully sync. |
| 1321 | # |
| 1322 | # The first instance should get the create and delete FSEvents |
| 1323 | # for each pair. Status should update the index with a new FSM |
| 1324 | # token (so the next invocation will not see data for these |
| 1325 | # events). |
| 1326 | |
| 1327 | GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try1.log" \ |
| 1328 | git -C file_case_wrong status --short \ |
| 1329 | >"$PWD/file_case_wrong-try1.out" && |
| 1330 | grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try1.log" && |
| 1331 | grep -q "fsmonitor_refresh_callback.*file-3-a.*pos 4" "$PWD/file_case_wrong-try1.log" && |
| 1332 | grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos 6" "$PWD/file_case_wrong-try1.log" && |
| 1333 | grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try1.log" && |
| 1334 | |
| 1335 | # FSM refresh will have invalidated the FSM bit and cause a regular |
| 1336 | # (real) scan of these tracked files, so they should have "H" status. |
| 1337 | # (We will not see a "h" status until the next refresh (on the next |
| 1338 | # command).) |
| 1339 | |
| 1340 | git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf1.out" && |
| 1341 | grep -q "H dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf1.out" && |
| 1342 | grep -q "H dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf1.out" && |
| 1343 | |
| 1344 | |
| 1345 | # Try the status again. We assume that the above status command |
| 1346 | # advanced the token so that the next one will not see those events. |
| 1347 | |
| 1348 | GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try2.log" \ |
| 1349 | git -C file_case_wrong status --short \ |
| 1350 | >"$PWD/file_case_wrong-try2.out" && |
| 1351 | ! grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos" "$PWD/file_case_wrong-try2.log" && |
| 1352 | ! grep -q "fsmonitor_refresh_callback.*file-3-a.*pos" "$PWD/file_case_wrong-try2.log" && |
| 1353 | ! grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos" "$PWD/file_case_wrong-try2.log" && |
| 1354 | ! grep -q "fsmonitor_refresh_callback.*file-4-a.*pos" "$PWD/file_case_wrong-try2.log" && |
| 1355 | |
| 1356 | # FSM refresh saw nothing, so it will mark all files as valid, |
| 1357 | # so they should now have "h" status. |
| 1358 | |
| 1359 | git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf2.out" && |
| 1360 | grep -q "h dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf2.out" && |
| 1361 | grep -q "h dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf2.out" && |
| 1362 | |
| 1363 | |
| 1364 | # We now have files with clean content, but with case-incorrect |
| 1365 | # file names. Modify them to see if status properly reports |
| 1366 | # them. |
| 1367 | |
| 1368 | echo xx >>file_case_wrong/dir1/dir2/dir3/FILE-3-A && |
| 1369 | echo xx >>file_case_wrong/dir1/dir2/dir4/file-4-a && |
| 1370 | |
| 1371 | GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try3.log" \ |
| 1372 | git -C file_case_wrong --no-optional-locks status --short \ |
| 1373 | >"$PWD/file_case_wrong-try3.out" && |
| 1374 | |
| 1375 | # Verify that we get a mapping event to correct the case. |
| 1376 | grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir3/FILE-3-A.*dir1/dir2/dir3/file-3-a" \ |
| 1377 | "$PWD/file_case_wrong-try3.log" && |
| 1378 | grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir4/file-4-a.*dir1/dir2/dir4/FILE-4-A" \ |
| 1379 | "$PWD/file_case_wrong-try3.log" && |
| 1380 | |
| 1381 | # FSEvents are in observed case. |
| 1382 | grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try3.log" && |
| 1383 | grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try3.log" && |
| 1384 | |
| 1385 | # The refresh-callbacks should have caused "git status" to clear |
| 1386 | # the CE_FSMONITOR_VALID bit on each of those files and caused |
| 1387 | # the worktree scan to visit them and mark them as modified. |
| 1388 | grep -q " M dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-try3.out" && |
| 1389 | grep -q " M dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-try3.out" |
| 1390 | ' |
| 1391 | |
| 1392 | test_done |