3 test_description='built-in file system watcher'
7 git version --build-options | grep "feature:" | grep "fsmonitor--daemon" || {
8 skip_all="The built-in FSMonitor is not supported on this platform"
14 git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null
25 git $r fsmonitor--daemon start || return $?
26 git $r fsmonitor--daemon status || return $?
31 test_expect_success 'explicit daemon start and stop' '
32 test_when_finished "kill_repo test_explicit" &&
34 git init test_explicit &&
35 start_daemon test_explicit &&
37 git -C test_explicit fsmonitor--daemon stop &&
38 test_must_fail git -C test_explicit fsmonitor--daemon status
41 test_expect_success 'implicit daemon start' '
42 test_when_finished "kill_repo test_implicit" &&
44 git init test_implicit &&
45 test_must_fail git -C test_implicit fsmonitor--daemon status &&
47 # query will implicitly start the daemon.
49 # for test-script simplicity, we send a V1 timestamp rather than
50 # a V2 token. either way, the daemon response to any query contains
51 # a new V2 token. (the daemon may complain that we sent a V1 request,
52 # but this test case is only concerned with whether the daemon was
53 # implicitly started.)
55 GIT_TRACE2_EVENT="$PWD/.git/trace" \
56 test-tool -C test_implicit fsmonitor-client query --token 0 >actual &&
57 nul_to_q <actual >actual.filtered &&
58 grep "builtin:" actual.filtered &&
60 # confirm that a daemon was started in the background.
62 # since the mechanism for starting the background daemon is platform
63 # dependent, just confirm that the foreground command received a
64 # response from the daemon.
66 grep :\"query/response-length\" .git/trace &&
68 git -C test_implicit fsmonitor--daemon status &&
69 git -C test_implicit fsmonitor--daemon stop &&
70 test_must_fail git -C test_implicit fsmonitor--daemon status
73 test_expect_success 'implicit daemon stop (delete .git)' '
74 test_when_finished "kill_repo test_implicit_1" &&
76 git init test_implicit_1 &&
78 start_daemon test_implicit_1 &&
80 # deleting the .git directory will implicitly stop the daemon.
81 rm -rf test_implicit_1/.git &&
83 # Create an empty .git directory so that the following Git command
84 # will stay relative to the `-C` directory. Without this, the Git
85 # command will (override the requested -C argument) and crawl out
86 # to the containing Git source tree. This would make the test
87 # result dependent upon whether we were using fsmonitor on our
88 # development worktree.
91 mkdir test_implicit_1/.git &&
93 test_must_fail git -C test_implicit_1 fsmonitor--daemon status
96 test_expect_success 'implicit daemon stop (rename .git)' '
97 test_when_finished "kill_repo test_implicit_2" &&
99 git init test_implicit_2 &&
101 start_daemon test_implicit_2 &&
103 # renaming the .git directory will implicitly stop the daemon.
104 mv test_implicit_2/.git test_implicit_2/.xxx &&
106 # Create an empty .git directory so that the following Git command
107 # will stay relative to the `-C` directory. Without this, the Git
108 # command will (override the requested -C argument) and crawl out
109 # to the containing Git source tree. This would make the test
110 # result dependent upon whether we were using fsmonitor on our
111 # development worktree.
114 mkdir test_implicit_2/.git &&
116 test_must_fail git -C test_implicit_2 fsmonitor--daemon status
119 test_expect_success 'cannot start multiple daemons' '
120 test_when_finished "kill_repo test_multiple" &&
122 git init test_multiple &&
124 start_daemon test_multiple &&
126 test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual &&
127 grep "fsmonitor--daemon is already running" actual &&
129 git -C test_multiple fsmonitor--daemon stop &&
130 test_must_fail git -C test_multiple fsmonitor--daemon status
133 test_expect_success 'setup' '
152 cat >.gitignore <<-\EOF &&
160 git -c core.useBuiltinFSMonitor= add . &&
162 git -c core.useBuiltinFSMonitor= commit -m initial &&
164 git config core.useBuiltinFSMonitor true
167 test_expect_success 'update-index implicitly starts daemon' '
168 test_must_fail git fsmonitor--daemon status &&
170 GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \
171 git update-index --fsmonitor &&
173 git fsmonitor--daemon status &&
174 test_might_fail git fsmonitor--daemon stop &&
176 grep \"event\":\"start\".*\"fsmonitor--daemon\" .git/trace_implicit_1
179 test_expect_success 'status implicitly starts daemon' '
180 test_must_fail git fsmonitor--daemon status &&
182 GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \
183 git status >actual &&
185 git fsmonitor--daemon status &&
186 test_might_fail git fsmonitor--daemon stop &&
188 grep \"event\":\"start\".*\"fsmonitor--daemon\" .git/trace_implicit_2
193 echo 2 >dir1/modified
194 echo 3 >dir2/modified
212 mv dir1/rename dir1/renamed
213 mv dir2/rename dir2/renamed
216 file_to_directory() {
222 directory_to_file() {
228 git status >actual &&
229 GIT_INDEX_FILE=.git/fresh-index git read-tree master &&
230 GIT_INDEX_FILE=.git/fresh-index git -c core.useBuiltinFSMonitor= status >expect &&
231 test_cmp expect actual &&
237 # The next few test cases confirm that our fsmonitor daemon sees each type
238 # of OS filesystem notification that we care about. At this layer we just
239 # ensure we are getting the OS notifications and do not try to confirm what
240 # is reported by `git status`.
242 # We run a simple query after modifying the filesystem just to introduce
243 # a bit of a delay so that the trace logging from the daemon has time to
244 # get flushed to disk.
246 # We `reset` and `clean` at the bottom of each test (and before stopping the
247 # daemon) because these commands might implicitly restart the daemon.
249 clean_up_repo_and_stop_daemon () {
250 git reset --hard HEAD
252 git fsmonitor--daemon stop
256 test_expect_success 'edit some files' '
257 test_when_finished "clean_up_repo_and_stop_daemon" &&
260 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
261 export GIT_TRACE_FSMONITOR &&
268 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
270 grep "^event: dir1/modified$" .git/trace &&
271 grep "^event: dir2/modified$" .git/trace &&
272 grep "^event: modified$" .git/trace &&
273 grep "^event: dir1/untracked$" .git/trace
276 test_expect_success 'create some files' '
277 test_when_finished "clean_up_repo_and_stop_daemon" &&
280 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
281 export GIT_TRACE_FSMONITOR &&
288 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
290 grep "^event: dir1/new$" .git/trace &&
291 grep "^event: dir2/new$" .git/trace &&
292 grep "^event: new$" .git/trace
295 test_expect_success 'delete some files' '
296 test_when_finished "clean_up_repo_and_stop_daemon" &&
299 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
300 export GIT_TRACE_FSMONITOR &&
307 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
309 grep "^event: dir1/delete$" .git/trace &&
310 grep "^event: dir2/delete$" .git/trace &&
311 grep "^event: delete$" .git/trace
314 test_expect_success 'rename some files' '
315 test_when_finished "clean_up_repo_and_stop_daemon" &&
318 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
319 export GIT_TRACE_FSMONITOR &&
326 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
328 grep "^event: dir1/rename$" .git/trace &&
329 grep "^event: dir2/rename$" .git/trace &&
330 grep "^event: rename$" .git/trace &&
331 grep "^event: dir1/renamed$" .git/trace &&
332 grep "^event: dir2/renamed$" .git/trace &&
333 grep "^event: renamed$" .git/trace
336 test_expect_success 'rename directory' '
337 test_when_finished "clean_up_repo_and_stop_daemon" &&
340 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
341 export GIT_TRACE_FSMONITOR &&
346 mv dirtorename dirrenamed &&
348 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
350 grep "^event: dirtorename/*$" .git/trace &&
351 grep "^event: dirrenamed/*$" .git/trace
354 test_expect_success 'file changes to directory' '
355 test_when_finished "clean_up_repo_and_stop_daemon" &&
358 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
359 export GIT_TRACE_FSMONITOR &&
366 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
368 grep "^event: delete$" .git/trace &&
369 grep "^event: delete/new$" .git/trace
372 test_expect_success 'directory changes to a file' '
373 test_when_finished "clean_up_repo_and_stop_daemon" &&
376 GIT_TRACE_FSMONITOR="$PWD/.git/trace" &&
377 export GIT_TRACE_FSMONITOR &&
384 test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
386 grep "^event: dir1$" .git/trace
389 # The next few test cases exercise the token-resync code. When filesystem
390 # drops events (because of filesystem velocity or because the daemon isn't
391 # polling fast enough), we need to discard the cached data (relative to the
392 # current token) and start collecting events under a new token.
394 # the 'test-tool fsmonitor-client flush' command can be used to send a
395 # "flush" message to a running daemon and ask it to do a flush/resync.
397 test_expect_success 'flush cached data' '
398 test_when_finished "kill_repo test_flush" &&
400 git init test_flush &&
403 GIT_TEST_FSMONITOR_TOKEN=true &&
404 export GIT_TEST_FSMONITOR_TOKEN &&
406 GIT_TRACE_FSMONITOR="$PWD/.git/trace_daemon" &&
407 export GIT_TRACE_FSMONITOR &&
409 start_daemon test_flush
412 # The daemon should have an initial token with no events in _0 and
413 # then a few (probably platform-specific number of) events in _1.
414 # These should both have the same <token_id>.
416 test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 &&
417 nul_to_q <actual_0 >actual_q0 &&
419 touch test_flush/file_1 &&
420 touch test_flush/file_2 &&
422 test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 &&
423 nul_to_q <actual_1 >actual_q1 &&
425 grep "file_1" actual_q1 &&
427 # Force a flush. This will change the <token_id>, reset the <seq_nr>, and
428 # flush the file data. Then create some events and ensure that the file
429 # again appears in the cache. It should have the new <token_id>.
431 test-tool -C test_flush fsmonitor-client flush >flush_0 &&
432 nul_to_q <flush_0 >flush_q0 &&
433 grep "^builtin:test_00000002:0Q/Q$" flush_q0 &&
435 test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 &&
436 nul_to_q <actual_2 >actual_q2 &&
438 grep "^builtin:test_00000002:0Q$" actual_q2 &&
440 touch test_flush/file_3 &&
442 test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 &&
443 nul_to_q <actual_3 >actual_q3 &&
445 grep "file_3" actual_q3
448 # The next few test cases create repos where the .git directory is NOT
449 # inside the one of the working directory. That is, where .git is a file
450 # that points to a directory elsewhere. This happens for submodules and
451 # non-primary worktrees.
453 test_expect_success 'setup worktree base' '
455 echo 1 >wt-base/file1 &&
456 git -C wt-base add file1 &&
457 git -C wt-base commit -m "c1"
460 test_expect_success 'worktree with .git file' '
461 git -C wt-base worktree add ../wt-secondary &&
464 GIT_TRACE2_PERF="$PWD/trace2_wt_secondary" &&
465 export GIT_TRACE2_PERF &&
467 GIT_TRACE_FSMONITOR="$PWD/trace_wt_secondary" &&
468 export GIT_TRACE_FSMONITOR &&
470 start_daemon wt-secondary
473 git -C wt-secondary fsmonitor--daemon stop &&
474 test_must_fail git -C wt-secondary fsmonitor--daemon status
477 # TODO Repeat one of the "edit" tests on wt-secondary and confirm that
478 # TODO we get the same events and behavior -- that is, that fsmonitor--daemon
479 # TODO correctly listens to events on both the working directory and to the
480 # TODO referenced GITDIR.
482 test_expect_success 'cleanup worktrees' '
483 kill_repo wt-secondary &&
487 # The next few tests perform arbitrary/contrived file operations and
488 # confirm that status is correct. That is, that the data (or lack of
489 # data) from fsmonitor doesn't cause incorrect results. And doesn't
490 # cause incorrect results when the untracked-cache is enabled.
492 test_lazy_prereq UNTRACKED_CACHE '
493 { git update-index --test-untracked-cache; ret=$?; } &&
497 test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' '
498 test_might_fail git config --unset core.useBuiltinFSMonitor &&
499 git update-index --no-fsmonitor &&
500 test_might_fail git fsmonitor--daemon stop
503 matrix_clean_up_repo () {
504 git reset --hard HEAD
513 test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" '
514 matrix_clean_up_repo &&
516 if test $uc = false -a $fsm = false
518 git status --porcelain=v1 >.git/expect.$fn
520 git status --porcelain=v1 >.git/actual.$fn
521 test_cmp .git/expect.$fn .git/actual.$fn
529 test_have_prereq UNTRACKED_CACHE && uc_values="false true"
530 for uc_val in $uc_values
532 if test $uc_val = false
534 test_expect_success "Matrix[uc:$uc_val] disable untracked cache" '
535 git config core.untrackedcache false &&
536 git update-index --no-untracked-cache
539 test_expect_success "Matrix[uc:$uc_val] enable untracked cache" '
540 git config core.untrackedcache true &&
541 git update-index --untracked-cache
545 fsm_values="false true"
546 for fsm_val in $fsm_values
548 if test $fsm_val = false
550 test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor" '
551 test_might_fail git config --unset core.useBuiltinFSMonitor &&
552 git update-index --no-fsmonitor &&
553 test_might_fail git fsmonitor--daemon stop 2>/dev/null
556 test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] enable fsmonitor" '
557 git config core.useBuiltinFSMonitor true &&
558 git fsmonitor--daemon start &&
559 git update-index --fsmonitor
563 matrix_try $uc_val $fsm_val edit_files
564 matrix_try $uc_val $fsm_val delete_files
565 matrix_try $uc_val $fsm_val create_files
566 matrix_try $uc_val $fsm_val rename_files
567 matrix_try $uc_val $fsm_val file_to_directory
568 matrix_try $uc_val $fsm_val directory_to_file