| #!/bin/sh | 
 |  | 
 | test_description='built-in file system watcher' | 
 |  | 
 | . ./test-lib.sh | 
 |  | 
 | if ! test_have_prereq FSMONITOR_DAEMON | 
 | then | 
 | 	skip_all="fsmonitor--daemon is not supported on this platform" | 
 | 	test_done | 
 | fi | 
 |  | 
 | stop_daemon_delete_repo () { | 
 | 	r=$1 && | 
 | 	test_might_fail git -C $r fsmonitor--daemon stop && | 
 | 	rm -rf $1 | 
 | } | 
 |  | 
 | start_daemon () { | 
 | 	r= tf= t2= tk= && | 
 |  | 
 | 	while test "$#" -ne 0 | 
 | 	do | 
 | 		case "$1" in | 
 | 		-C) | 
 | 			r="-C ${2?}" | 
 | 			shift | 
 | 			;; | 
 | 		--tf) | 
 | 			tf="${2?}" | 
 | 			shift | 
 | 			;; | 
 | 		--t2) | 
 | 			t2="${2?}" | 
 | 			shift | 
 | 			;; | 
 | 		--tk) | 
 | 			tk="${2?}" | 
 | 			shift | 
 | 			;; | 
 | 		-*) | 
 | 			BUG "error: unknown option: '$1'" | 
 | 			;; | 
 | 		*) | 
 | 			BUG "error: unbound argument: '$1'" | 
 | 			;; | 
 | 		esac | 
 | 		shift | 
 | 	done && | 
 |  | 
 | 	( | 
 | 		if test -n "$tf" | 
 | 		then | 
 | 			GIT_TRACE_FSMONITOR="$tf" | 
 | 			export GIT_TRACE_FSMONITOR | 
 | 		fi && | 
 |  | 
 | 		if test -n "$t2" | 
 | 		then | 
 | 			GIT_TRACE2_PERF="$t2" | 
 | 			export GIT_TRACE2_PERF | 
 | 		fi && | 
 |  | 
 | 		if test -n "$tk" | 
 | 		then | 
 | 			GIT_TEST_FSMONITOR_TOKEN="$tk" | 
 | 			export GIT_TEST_FSMONITOR_TOKEN | 
 | 		fi && | 
 |  | 
 | 		git $r fsmonitor--daemon start && | 
 | 		git $r fsmonitor--daemon status | 
 | 	) | 
 | } | 
 |  | 
 | # Is a Trace2 data event present with the given catetory and key? | 
 | # We do not care what the value is. | 
 | # | 
 | have_t2_data_event () { | 
 | 	c=$1 && | 
 | 	k=$2 && | 
 |  | 
 | 	grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' | 
 | } | 
 |  | 
 | test_expect_success 'explicit daemon start and stop' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_explicit" && | 
 |  | 
 | 	git init test_explicit && | 
 | 	start_daemon -C test_explicit && | 
 |  | 
 | 	git -C test_explicit fsmonitor--daemon stop && | 
 | 	test_must_fail git -C test_explicit fsmonitor--daemon status | 
 | ' | 
 |  | 
 | test_expect_success 'implicit daemon start' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_implicit" && | 
 |  | 
 | 	git init test_implicit && | 
 | 	test_must_fail git -C test_implicit fsmonitor--daemon status && | 
 |  | 
 | 	# query will implicitly start the daemon. | 
 | 	# | 
 | 	# for test-script simplicity, we send a V1 timestamp rather than | 
 | 	# a V2 token.  either way, the daemon response to any query contains | 
 | 	# a new V2 token.  (the daemon may complain that we sent a V1 request, | 
 | 	# but this test case is only concerned with whether the daemon was | 
 | 	# implicitly started.) | 
 |  | 
 | 	GIT_TRACE2_EVENT="$PWD/.git/trace" \ | 
 | 		test-tool -C test_implicit fsmonitor-client query --token 0 >actual && | 
 | 	nul_to_q <actual >actual.filtered && | 
 | 	grep "builtin:" actual.filtered && | 
 |  | 
 | 	# confirm that a daemon was started in the background. | 
 | 	# | 
 | 	# since the mechanism for starting the background daemon is platform | 
 | 	# dependent, just confirm that the foreground command received a | 
 | 	# response from the daemon. | 
 |  | 
 | 	have_t2_data_event fsm_client query/response-length <.git/trace && | 
 |  | 
 | 	git -C test_implicit fsmonitor--daemon status && | 
 | 	git -C test_implicit fsmonitor--daemon stop && | 
 | 	test_must_fail git -C test_implicit fsmonitor--daemon status | 
 | ' | 
 |  | 
 | # Verify that the daemon has shutdown.  Spin a few seconds to | 
 | # make the test a little more robust during CI testing. | 
 | # | 
 | # We're looking for an implicit shutdown, such as when we delete or | 
 | # rename the ".git" directory.  Our delete/rename will cause a file | 
 | # system event that the daemon will see and the daemon will | 
 | # auto-shutdown as soon as it sees it.  But this is racy with our `git | 
 | # fsmonitor--daemon status` commands (and we cannot use a cookie file | 
 | # here to help us).  So spin a little and give the daemon a chance to | 
 | # see the event.  (This is primarily for underpowered CI build/test | 
 | # machines (where it might take a moment to wake and reschedule the | 
 | # daemon process) to avoid false alarms during test runs.) | 
 | # | 
 | IMPLICIT_TIMEOUT=5 | 
 |  | 
 | verify_implicit_shutdown () { | 
 | 	r=$1 && | 
 |  | 
 | 	k=0 && | 
 | 	while test "$k" -lt $IMPLICIT_TIMEOUT | 
 | 	do | 
 | 		git -C $r fsmonitor--daemon status || return 0 | 
 |  | 
 | 		sleep 1 | 
 | 		k=$(( $k + 1 )) | 
 | 	done && | 
 |  | 
 | 	return 1 | 
 | } | 
 |  | 
 | test_expect_success 'implicit daemon stop (delete .git)' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_implicit_1" && | 
 |  | 
 | 	git init test_implicit_1 && | 
 |  | 
 | 	start_daemon -C test_implicit_1 && | 
 |  | 
 | 	# deleting the .git directory will implicitly stop the daemon. | 
 | 	rm -rf test_implicit_1/.git && | 
 |  | 
 | 	# [1] Create an empty .git directory so that the following Git | 
 | 	#     command will stay relative to the `-C` directory. | 
 | 	# | 
 | 	#     Without this, the Git command will override the requested | 
 | 	#     -C argument and crawl out to the containing Git source tree. | 
 | 	#     This would make the test result dependent upon whether we | 
 | 	#     were using fsmonitor on our development worktree. | 
 | 	# | 
 | 	mkdir test_implicit_1/.git && | 
 |  | 
 | 	verify_implicit_shutdown test_implicit_1 | 
 | ' | 
 |  | 
 | test_expect_success 'implicit daemon stop (rename .git)' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_implicit_2" && | 
 |  | 
 | 	git init test_implicit_2 && | 
 |  | 
 | 	start_daemon -C test_implicit_2 && | 
 |  | 
 | 	# renaming the .git directory will implicitly stop the daemon. | 
 | 	mv test_implicit_2/.git test_implicit_2/.xxx && | 
 |  | 
 | 	# See [1] above. | 
 | 	# | 
 | 	mkdir test_implicit_2/.git && | 
 |  | 
 | 	verify_implicit_shutdown test_implicit_2 | 
 | ' | 
 |  | 
 | # File systems on Windows may or may not have shortnames. | 
 | # This is a volume-specific setting on modern systems. | 
 | # "C:/" drives are required to have them enabled.  Other | 
 | # hard drives default to disabled. | 
 | # | 
 | # This is a crude test to see if shortnames are enabled | 
 | # on the volume containing the test directory.  It is | 
 | # crude, but it does not require elevation like `fsutil`. | 
 | # | 
 | test_lazy_prereq SHORTNAMES ' | 
 | 	mkdir .foo && | 
 | 	test -d "FOO~1" | 
 | ' | 
 |  | 
 | # Here we assume that the shortname of ".git" is "GIT~1". | 
 | test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~1)' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_implicit_1s" && | 
 |  | 
 | 	git init test_implicit_1s && | 
 |  | 
 | 	start_daemon -C test_implicit_1s && | 
 |  | 
 | 	# renaming the .git directory will implicitly stop the daemon. | 
 | 	# this moves {.git, GIT~1} to {.gitxyz, GITXYZ~1}. | 
 | 	# the rename-from FS Event will contain the shortname. | 
 | 	# | 
 | 	mv test_implicit_1s/GIT~1 test_implicit_1s/.gitxyz && | 
 |  | 
 | 	# See [1] above. | 
 | 	# this moves {.gitxyz, GITXYZ~1} to {.git, GIT~1}. | 
 | 	mv test_implicit_1s/.gitxyz test_implicit_1s/.git && | 
 |  | 
 | 	verify_implicit_shutdown test_implicit_1s | 
 | ' | 
 |  | 
 | # Here we first create a file with LONGNAME of "GIT~1" before | 
 | # we create the repo.  This will cause the shortname of ".git" | 
 | # to be "GIT~2". | 
 | test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_implicit_1s2" && | 
 |  | 
 | 	mkdir test_implicit_1s2 && | 
 | 	echo HELLO >test_implicit_1s2/GIT~1 && | 
 | 	git init test_implicit_1s2 && | 
 |  | 
 | 	test_path_is_file test_implicit_1s2/GIT~1 && | 
 | 	test_path_is_dir  test_implicit_1s2/GIT~2 && | 
 |  | 
 | 	start_daemon -C test_implicit_1s2 && | 
 |  | 
 | 	# renaming the .git directory will implicitly stop the daemon. | 
 | 	# the rename-from FS Event will contain the shortname. | 
 | 	# | 
 | 	mv test_implicit_1s2/GIT~2 test_implicit_1s2/.gitxyz && | 
 |  | 
 | 	# See [1] above. | 
 | 	mv test_implicit_1s2/.gitxyz test_implicit_1s2/.git && | 
 |  | 
 | 	verify_implicit_shutdown test_implicit_1s2 | 
 | ' | 
 |  | 
 | test_expect_success 'cannot start multiple daemons' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_multiple" && | 
 |  | 
 | 	git init test_multiple && | 
 |  | 
 | 	start_daemon -C test_multiple && | 
 |  | 
 | 	test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && | 
 | 	grep "fsmonitor--daemon is already running" actual && | 
 |  | 
 | 	git -C test_multiple fsmonitor--daemon stop && | 
 | 	test_must_fail git -C test_multiple fsmonitor--daemon status | 
 | ' | 
 |  | 
 | # These tests use the main repo in the trash directory | 
 |  | 
 | test_expect_success 'setup' ' | 
 | 	>tracked && | 
 | 	>modified && | 
 | 	>delete && | 
 | 	>rename && | 
 | 	mkdir dir1 && | 
 | 	>dir1/tracked && | 
 | 	>dir1/modified && | 
 | 	>dir1/delete && | 
 | 	>dir1/rename && | 
 | 	mkdir dir2 && | 
 | 	>dir2/tracked && | 
 | 	>dir2/modified && | 
 | 	>dir2/delete && | 
 | 	>dir2/rename && | 
 | 	mkdir dirtorename && | 
 | 	>dirtorename/a && | 
 | 	>dirtorename/b && | 
 |  | 
 | 	cat >.gitignore <<-\EOF && | 
 | 	.gitignore | 
 | 	expect* | 
 | 	actual* | 
 | 	flush* | 
 | 	trace* | 
 | 	EOF | 
 |  | 
 | 	mkdir -p T1/T2/T3/T4 && | 
 | 	echo 1 >T1/F1 && | 
 | 	echo 1 >T1/T2/F1 && | 
 | 	echo 1 >T1/T2/T3/F1 && | 
 | 	echo 1 >T1/T2/T3/T4/F1 && | 
 | 	echo 2 >T1/F2 && | 
 | 	echo 2 >T1/T2/F2 && | 
 | 	echo 2 >T1/T2/T3/F2 && | 
 | 	echo 2 >T1/T2/T3/T4/F2 && | 
 |  | 
 | 	git -c core.fsmonitor=false add . && | 
 | 	test_tick && | 
 | 	git -c core.fsmonitor=false commit -m initial && | 
 |  | 
 | 	git config core.fsmonitor true | 
 | ' | 
 |  | 
 | # The test already explicitly stopped (or tried to stop) the daemon. | 
 | # This is here in case something else fails first. | 
 | # | 
 | redundant_stop_daemon () { | 
 | 	test_might_fail git fsmonitor--daemon stop | 
 | } | 
 |  | 
 | test_expect_success 'update-index implicitly starts daemon' ' | 
 | 	test_when_finished redundant_stop_daemon && | 
 |  | 
 | 	test_must_fail git fsmonitor--daemon status && | 
 |  | 
 | 	GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \ | 
 | 		git update-index --fsmonitor && | 
 |  | 
 | 	git fsmonitor--daemon status && | 
 | 	test_might_fail git fsmonitor--daemon stop && | 
 |  | 
 | 	# Confirm that the trace2 log contains a record of the | 
 | 	# daemon starting. | 
 | 	test_subcommand git fsmonitor--daemon start <.git/trace_implicit_1 | 
 | ' | 
 |  | 
 | test_expect_success 'status implicitly starts daemon' ' | 
 | 	test_when_finished redundant_stop_daemon && | 
 |  | 
 | 	test_must_fail git fsmonitor--daemon status && | 
 |  | 
 | 	GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \ | 
 | 		git status >actual && | 
 |  | 
 | 	git fsmonitor--daemon status && | 
 | 	test_might_fail git fsmonitor--daemon stop && | 
 |  | 
 | 	# Confirm that the trace2 log contains a record of the | 
 | 	# daemon starting. | 
 | 	test_subcommand git fsmonitor--daemon start <.git/trace_implicit_2 | 
 | ' | 
 |  | 
 | edit_files () { | 
 | 	echo 1 >modified && | 
 | 	echo 2 >dir1/modified && | 
 | 	echo 3 >dir2/modified && | 
 | 	>dir1/untracked | 
 | } | 
 |  | 
 | delete_files () { | 
 | 	rm -f delete && | 
 | 	rm -f dir1/delete && | 
 | 	rm -f dir2/delete | 
 | } | 
 |  | 
 | create_files () { | 
 | 	echo 1 >new && | 
 | 	echo 2 >dir1/new && | 
 | 	echo 3 >dir2/new | 
 | } | 
 |  | 
 | rename_files () { | 
 | 	mv rename renamed && | 
 | 	mv dir1/rename dir1/renamed && | 
 | 	mv dir2/rename dir2/renamed | 
 | } | 
 |  | 
 | file_to_directory () { | 
 | 	rm -f delete && | 
 | 	mkdir delete && | 
 | 	echo 1 >delete/new | 
 | } | 
 |  | 
 | directory_to_file () { | 
 | 	rm -rf dir1 && | 
 | 	echo 1 >dir1 | 
 | } | 
 |  | 
 | move_directory_contents_deeper() { | 
 | 	mkdir T1/_new_ && | 
 | 	mv T1/[A-Z]* T1/_new_ | 
 | } | 
 |  | 
 | move_directory_up() { | 
 | 	mv T1/T2/T3 T1 | 
 | } | 
 |  | 
 | move_directory() { | 
 | 	mv T1/T2/T3 T1/T2/NewT3 | 
 | } | 
 |  | 
 | # The next few test cases confirm that our fsmonitor daemon sees each type | 
 | # of OS filesystem notification that we care about.  At this layer we just | 
 | # ensure we are getting the OS notifications and do not try to confirm what | 
 | # is reported by `git status`. | 
 | # | 
 | # We run a simple query after modifying the filesystem just to introduce | 
 | # a bit of a delay so that the trace logging from the daemon has time to | 
 | # get flushed to disk. | 
 | # | 
 | # We `reset` and `clean` at the bottom of each test (and before stopping the | 
 | # daemon) because these commands might implicitly restart the daemon. | 
 |  | 
 | clean_up_repo_and_stop_daemon () { | 
 | 	git reset --hard HEAD && | 
 | 	git clean -fd && | 
 | 	test_might_fail git fsmonitor--daemon stop && | 
 | 	rm -f .git/trace | 
 | } | 
 |  | 
 | test_expect_success 'edit some files' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	edit_files && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dir1/modified$"  .git/trace && | 
 | 	grep "^event: dir2/modified$"  .git/trace && | 
 | 	grep "^event: modified$"       .git/trace && | 
 | 	grep "^event: dir1/untracked$" .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'create some files' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	create_files && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dir1/new$" .git/trace && | 
 | 	grep "^event: dir2/new$" .git/trace && | 
 | 	grep "^event: new$"      .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'delete some files' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	delete_files && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dir1/delete$" .git/trace && | 
 | 	grep "^event: dir2/delete$" .git/trace && | 
 | 	grep "^event: delete$"      .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'rename some files' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	rename_files && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dir1/rename$"  .git/trace && | 
 | 	grep "^event: dir2/rename$"  .git/trace && | 
 | 	grep "^event: rename$"       .git/trace && | 
 | 	grep "^event: dir1/renamed$" .git/trace && | 
 | 	grep "^event: dir2/renamed$" .git/trace && | 
 | 	grep "^event: renamed$"      .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'rename directory' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	mv dirtorename dirrenamed && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dirtorename/*$" .git/trace && | 
 | 	grep "^event: dirrenamed/*$"  .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'file changes to directory' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	file_to_directory && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: delete$"     .git/trace && | 
 | 	grep "^event: delete/new$" .git/trace | 
 | ' | 
 |  | 
 | test_expect_success 'directory changes to a file' ' | 
 | 	test_when_finished clean_up_repo_and_stop_daemon && | 
 |  | 
 | 	start_daemon --tf "$PWD/.git/trace" && | 
 |  | 
 | 	directory_to_file && | 
 |  | 
 | 	test-tool fsmonitor-client query --token 0 && | 
 |  | 
 | 	grep "^event: dir1$" .git/trace | 
 | ' | 
 |  | 
 | # The next few test cases exercise the token-resync code.  When filesystem | 
 | # drops events (because of filesystem velocity or because the daemon isn't | 
 | # polling fast enough), we need to discard the cached data (relative to the | 
 | # current token) and start collecting events under a new token. | 
 | # | 
 | # the 'test-tool fsmonitor-client flush' command can be used to send a | 
 | # "flush" message to a running daemon and ask it to do a flush/resync. | 
 |  | 
 | test_expect_success 'flush cached data' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_flush" && | 
 |  | 
 | 	git init test_flush && | 
 |  | 
 | 	start_daemon -C test_flush --tf "$PWD/.git/trace_daemon" --tk true && | 
 |  | 
 | 	# The daemon should have an initial token with no events in _0 and | 
 | 	# then a few (probably platform-specific number of) events in _1. | 
 | 	# These should both have the same <token_id>. | 
 |  | 
 | 	test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 && | 
 | 	nul_to_q <actual_0 >actual_q0 && | 
 |  | 
 | 	>test_flush/file_1 && | 
 | 	>test_flush/file_2 && | 
 |  | 
 | 	test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && | 
 | 	nul_to_q <actual_1 >actual_q1 && | 
 |  | 
 | 	grep "file_1" actual_q1 && | 
 |  | 
 | 	# Force a flush.  This will change the <token_id>, reset the <seq_nr>, and | 
 | 	# flush the file data.  Then create some events and ensure that the file | 
 | 	# again appears in the cache.  It should have the new <token_id>. | 
 |  | 
 | 	test-tool -C test_flush fsmonitor-client flush >flush_0 && | 
 | 	nul_to_q <flush_0 >flush_q0 && | 
 | 	grep "^builtin:test_00000002:0Q/Q$" flush_q0 && | 
 |  | 
 | 	test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 && | 
 | 	nul_to_q <actual_2 >actual_q2 && | 
 |  | 
 | 	grep "^builtin:test_00000002:0Q$" actual_q2 && | 
 |  | 
 | 	>test_flush/file_3 && | 
 |  | 
 | 	test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 && | 
 | 	nul_to_q <actual_3 >actual_q3 && | 
 |  | 
 | 	grep "file_3" actual_q3 | 
 | ' | 
 |  | 
 | # The next few test cases create repos where the .git directory is NOT | 
 | # inside the one of the working directory.  That is, where .git is a file | 
 | # that points to a directory elsewhere.  This happens for submodules and | 
 | # non-primary worktrees. | 
 |  | 
 | test_expect_success 'setup worktree base' ' | 
 | 	git init wt-base && | 
 | 	echo 1 >wt-base/file1 && | 
 | 	git -C wt-base add file1 && | 
 | 	git -C wt-base commit -m "c1" | 
 | ' | 
 |  | 
 | test_expect_success 'worktree with .git file' ' | 
 | 	git -C wt-base worktree add ../wt-secondary && | 
 |  | 
 | 	start_daemon -C wt-secondary \ | 
 | 		--tf "$PWD/trace_wt_secondary" \ | 
 | 		--t2 "$PWD/trace2_wt_secondary" && | 
 |  | 
 | 	git -C wt-secondary fsmonitor--daemon stop && | 
 | 	test_must_fail git -C wt-secondary fsmonitor--daemon status | 
 | ' | 
 |  | 
 | # NEEDSWORK: Repeat one of the "edit" tests on wt-secondary and | 
 | # confirm that we get the same events and behavior -- that is, that | 
 | # fsmonitor--daemon correctly watches BOTH the working directory and | 
 | # the external GITDIR directory and behaves the same as when ".git" | 
 | # is a directory inside the working directory. | 
 |  | 
 | test_expect_success 'cleanup worktrees' ' | 
 | 	stop_daemon_delete_repo wt-secondary && | 
 | 	stop_daemon_delete_repo wt-base | 
 | ' | 
 |  | 
 | # The next few tests perform arbitrary/contrived file operations and | 
 | # confirm that status is correct.  That is, that the data (or lack of | 
 | # data) from fsmonitor doesn't cause incorrect results.  And doesn't | 
 | # cause incorrect results when the untracked-cache is enabled. | 
 |  | 
 | test_lazy_prereq UNTRACKED_CACHE ' | 
 | 	git update-index --test-untracked-cache | 
 | ' | 
 |  | 
 | test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' ' | 
 | 	test_unconfig core.fsmonitor && | 
 | 	git update-index --no-fsmonitor && | 
 | 	test_might_fail git fsmonitor--daemon stop | 
 | ' | 
 |  | 
 | matrix_clean_up_repo () { | 
 | 	git reset --hard HEAD && | 
 | 	git clean -fd | 
 | } | 
 |  | 
 | matrix_try () { | 
 | 	uc=$1 && | 
 | 	fsm=$2 && | 
 | 	fn=$3 && | 
 |  | 
 | 	if test $uc = true && test $fsm = false | 
 | 	then | 
 | 		# The untracked-cache is buggy when FSMonitor is | 
 | 		# DISABLED, so skip the tests for this matrix | 
 | 		# combination. | 
 | 		# | 
 | 		# We've observed random, occasional test failures on | 
 | 		# Windows and MacOS when the UC is turned on and FSM | 
 | 		# is turned off.  These are rare, but they do happen | 
 | 		# indicating that it is probably a race condition within | 
 | 		# the untracked cache itself. | 
 | 		# | 
 | 		# It usually happens when a test does F/D trickery and | 
 | 		# then the NEXT test fails because of extra status | 
 | 		# output from stale UC data from the previous test. | 
 | 		# | 
 | 		# Since FSMonitor is not involved in the error, skip | 
 | 		# the tests for this matrix combination. | 
 | 		# | 
 | 		return 0 | 
 | 	fi && | 
 |  | 
 | 	test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' | 
 | 		matrix_clean_up_repo && | 
 | 		$fn && | 
 | 		if test $uc = false && test $fsm = false | 
 | 		then | 
 | 			git status --porcelain=v1 >.git/expect.$fn | 
 | 		else | 
 | 			git status --porcelain=v1 >.git/actual.$fn && | 
 | 			test_cmp .git/expect.$fn .git/actual.$fn | 
 | 		fi | 
 | 	' | 
 | } | 
 |  | 
 | uc_values="false" | 
 | test_have_prereq UNTRACKED_CACHE && uc_values="false true" | 
 | for uc_val in $uc_values | 
 | do | 
 | 	if test $uc_val = false | 
 | 	then | 
 | 		test_expect_success "Matrix[uc:$uc_val] disable untracked cache" ' | 
 | 			git config core.untrackedcache false && | 
 | 			git update-index --no-untracked-cache | 
 | 		' | 
 | 	else | 
 | 		test_expect_success "Matrix[uc:$uc_val] enable untracked cache" ' | 
 | 			git config core.untrackedcache true && | 
 | 			git update-index --untracked-cache | 
 | 		' | 
 | 	fi | 
 |  | 
 | 	fsm_values="false true" | 
 | 	for fsm_val in $fsm_values | 
 | 	do | 
 | 		if test $fsm_val = false | 
 | 		then | 
 | 			test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor" ' | 
 | 				test_unconfig core.fsmonitor && | 
 | 				git update-index --no-fsmonitor && | 
 | 				test_might_fail git fsmonitor--daemon stop | 
 | 			' | 
 | 		else | 
 | 			test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] enable fsmonitor" ' | 
 | 				git config core.fsmonitor true && | 
 | 				git fsmonitor--daemon start && | 
 | 				git update-index --fsmonitor | 
 | 			' | 
 | 		fi | 
 |  | 
 | 		matrix_try $uc_val $fsm_val edit_files | 
 | 		matrix_try $uc_val $fsm_val delete_files | 
 | 		matrix_try $uc_val $fsm_val create_files | 
 | 		matrix_try $uc_val $fsm_val rename_files | 
 | 		matrix_try $uc_val $fsm_val file_to_directory | 
 | 		matrix_try $uc_val $fsm_val directory_to_file | 
 |  | 
 | 		matrix_try $uc_val $fsm_val move_directory_contents_deeper | 
 | 		matrix_try $uc_val $fsm_val move_directory_up | 
 | 		matrix_try $uc_val $fsm_val move_directory | 
 |  | 
 | 		if test $fsm_val = true | 
 | 		then | 
 | 			test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" ' | 
 | 				test_unconfig core.fsmonitor && | 
 | 				git update-index --no-fsmonitor && | 
 | 				test_might_fail git fsmonitor--daemon stop | 
 | 			' | 
 | 		fi | 
 | 	done | 
 | done | 
 |  | 
 | # Test Unicode UTF-8 characters in the pathname of the working | 
 | # directory root.  Use of "*A()" routines rather than "*W()" routines | 
 | # on Windows can sometimes lead to odd failures. | 
 | # | 
 | u1=$(printf "u_c3_a6__\xC3\xA6") | 
 | u2=$(printf "u_e2_99_ab__\xE2\x99\xAB") | 
 | u_values="$u1 $u2" | 
 | for u in $u_values | 
 | do | 
 | 	test_expect_success "unicode in repo root path: $u" ' | 
 | 		test_when_finished "stop_daemon_delete_repo $u" && | 
 |  | 
 | 		git init "$u" && | 
 | 		echo 1 >"$u"/file1 && | 
 | 		git -C "$u" add file1 && | 
 | 		git -C "$u" config core.fsmonitor true && | 
 |  | 
 | 		start_daemon -C "$u" && | 
 | 		git -C "$u" status >actual && | 
 | 		grep "new file:   file1" actual | 
 | 	' | 
 | done | 
 |  | 
 | # Test fsmonitor interaction with submodules. | 
 | # | 
 | # If we start the daemon in the super, it will see FS events for | 
 | # everything in the working directory cone and this includes any | 
 | # files/directories contained *within* the submodules. | 
 | # | 
 | # A `git status` at top level will get events for items within the | 
 | # submodule and ignore them, since they aren't named in the index | 
 | # of the super repo.  This makes the fsmonitor response a little | 
 | # noisy, but it doesn't alter the correctness of the state of the | 
 | # super-proper. | 
 | # | 
 | # When we have submodules, `git status` normally does a recursive | 
 | # status on each of the submodules and adds a summary row for any | 
 | # dirty submodules.  (See the "S..." bits in porcelain V2 output.) | 
 | # | 
 | # It is therefore important that the top level status not be tricked | 
 | # by the FSMonitor response to skip those recursive calls.  That is, | 
 | # even if FSMonitor says that the mtime of the submodule directory | 
 | # hasn't changed and it could be implicitly marked valid, we must | 
 | # not take that shortcut.  We need to force the recusion into the | 
 | # submodule so that we get a summary of the status *within* the | 
 | # submodule. | 
 |  | 
 | create_super () { | 
 | 	super="$1" && | 
 |  | 
 | 	git init "$super" && | 
 | 	echo x >"$super/file_1" && | 
 | 	echo y >"$super/file_2" && | 
 | 	echo z >"$super/file_3" && | 
 | 	mkdir "$super/dir_1" && | 
 | 	echo a >"$super/dir_1/file_11" && | 
 | 	echo b >"$super/dir_1/file_12" && | 
 | 	mkdir "$super/dir_1/dir_2" && | 
 | 	echo a >"$super/dir_1/dir_2/file_21" && | 
 | 	echo b >"$super/dir_1/dir_2/file_22" && | 
 | 	git -C "$super" add . && | 
 | 	git -C "$super" commit -m "initial $super commit" | 
 | } | 
 |  | 
 | create_sub () { | 
 | 	sub="$1" && | 
 |  | 
 | 	git init "$sub" && | 
 | 	echo x >"$sub/file_x" && | 
 | 	echo y >"$sub/file_y" && | 
 | 	echo z >"$sub/file_z" && | 
 | 	mkdir "$sub/dir_x" && | 
 | 	echo a >"$sub/dir_x/file_a" && | 
 | 	echo b >"$sub/dir_x/file_b" && | 
 | 	mkdir "$sub/dir_x/dir_y" && | 
 | 	echo a >"$sub/dir_x/dir_y/file_a" && | 
 | 	echo b >"$sub/dir_x/dir_y/file_b" && | 
 | 	git -C "$sub" add . && | 
 | 	git -C "$sub" commit -m "initial $sub commit" | 
 | } | 
 |  | 
 | my_match_and_clean () { | 
 | 	git -C super --no-optional-locks status --porcelain=v2 >actual.with && | 
 | 	git -C super --no-optional-locks -c core.fsmonitor=false \ | 
 | 		status --porcelain=v2 >actual.without && | 
 | 	test_cmp actual.with actual.without && | 
 |  | 
 | 	git -C super --no-optional-locks diff-index --name-status HEAD >actual.with && | 
 | 	git -C super --no-optional-locks -c core.fsmonitor=false \ | 
 | 		diff-index --name-status HEAD >actual.without && | 
 | 	test_cmp actual.with actual.without && | 
 |  | 
 | 	git -C super/dir_1/dir_2/sub reset --hard && | 
 | 	git -C super/dir_1/dir_2/sub clean -d -f | 
 | } | 
 |  | 
 | test_expect_success 'submodule setup' ' | 
 | 	git config --global protocol.file.allow always | 
 | ' | 
 |  | 
 | test_expect_success 'submodule always visited' ' | 
 | 	test_when_finished "git -C super fsmonitor--daemon stop; \ | 
 | 			    rm -rf super; \ | 
 | 			    rm -rf sub" && | 
 |  | 
 | 	create_super super && | 
 | 	create_sub sub && | 
 |  | 
 | 	git -C super submodule add ../sub ./dir_1/dir_2/sub && | 
 | 	git -C super commit -m "add sub" && | 
 |  | 
 | 	start_daemon -C super && | 
 | 	git -C super config core.fsmonitor true && | 
 | 	git -C super update-index --fsmonitor && | 
 | 	git -C super status && | 
 |  | 
 | 	# Now run pairs of commands w/ and w/o FSMonitor while we make | 
 | 	# some dirt in the submodule and confirm matching output. | 
 |  | 
 | 	# Completely clean status. | 
 | 	my_match_and_clean && | 
 |  | 
 | 	# .M S..U | 
 | 	echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_u && | 
 | 	my_match_and_clean && | 
 |  | 
 | 	# .M S.M. | 
 | 	echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_m && | 
 | 	git -C super/dir_1/dir_2/sub add . && | 
 | 	my_match_and_clean && | 
 |  | 
 | 	# .M S.M. | 
 | 	echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && | 
 | 	git -C super/dir_1/dir_2/sub add . && | 
 | 	my_match_and_clean && | 
 |  | 
 | 	# .M SC.. | 
 | 	echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && | 
 | 	git -C super/dir_1/dir_2/sub add . && | 
 | 	git -C super/dir_1/dir_2/sub commit -m "SC.." && | 
 | 	my_match_and_clean | 
 | ' | 
 |  | 
 | # If a submodule has a `sub/.git/` directory (rather than a file | 
 | # pointing to the super's `.git/modules/sub`) and `core.fsmonitor` | 
 | # turned on in the submodule and the daemon is not yet started in | 
 | # the submodule, and someone does a `git submodule absorbgitdirs` | 
 | # in the super, Git will recursively invoke `git submodule--helper` | 
 | # to do the work and this may try to read the index.  This will | 
 | # try to start the daemon in the submodule. | 
 |  | 
 | test_expect_success "submodule absorbgitdirs implicitly starts daemon" ' | 
 | 	test_when_finished "rm -rf super; \ | 
 | 			    rm -rf sub;   \ | 
 | 			    rm super-sub.trace" && | 
 |  | 
 | 	create_super super && | 
 | 	create_sub sub && | 
 |  | 
 | 	# Copy rather than submodule add so that we get a .git dir. | 
 | 	cp -R ./sub ./super/dir_1/dir_2/sub && | 
 |  | 
 | 	git -C super/dir_1/dir_2/sub config core.fsmonitor true && | 
 |  | 
 | 	git -C super submodule add ../sub ./dir_1/dir_2/sub && | 
 | 	git -C super commit -m "add sub" && | 
 |  | 
 | 	test_path_is_dir super/dir_1/dir_2/sub/.git && | 
 |  | 
 | 	cwd="$(cd super && pwd)" && | 
 | 	cat >expect <<-EOF && | 
 | 	Migrating git directory of '\''dir_1/dir_2/sub'\'' from | 
 | 	'\''$cwd/dir_1/dir_2/sub/.git'\'' to | 
 | 	'\''$cwd/.git/modules/dir_1/dir_2/sub'\'' | 
 | 	EOF | 
 | 	GIT_TRACE2_EVENT="$PWD/super-sub.trace" \ | 
 | 		git -C super submodule absorbgitdirs >out 2>actual && | 
 | 	test_cmp expect actual && | 
 | 	test_must_be_empty out && | 
 |  | 
 | 	# Confirm that the trace2 log contains a record of the | 
 | 	# daemon starting. | 
 | 	test_subcommand git fsmonitor--daemon start <super-sub.trace | 
 | ' | 
 |  | 
 | # On a case-insensitive file system, confirm that the daemon | 
 | # notices when the .git directory is moved/renamed/deleted | 
 | # regardless of how it is spelled in the FS event. | 
 | # That is, does the FS event receive the spelling of the | 
 | # operation or does it receive the spelling preserved with | 
 | # the file/directory. | 
 | # | 
 | test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_insensitive" && | 
 |  | 
 | 	git init test_insensitive && | 
 |  | 
 | 	start_daemon -C test_insensitive --tf "$PWD/insensitive.trace" && | 
 |  | 
 | 	mkdir -p test_insensitive/abc/def && | 
 | 	echo xyz >test_insensitive/ABC/DEF/xyz && | 
 |  | 
 | 	test_path_is_dir test_insensitive/.git && | 
 | 	test_path_is_dir test_insensitive/.GIT && | 
 |  | 
 | 	# Rename .git using an alternate spelling to verify that | 
 | 	# the daemon detects it and automatically shuts down. | 
 | 	mv test_insensitive/.GIT test_insensitive/.FOO && | 
 |  | 
 | 	# See [1] above. | 
 | 	mv test_insensitive/.FOO test_insensitive/.git && | 
 |  | 
 | 	verify_implicit_shutdown test_insensitive && | 
 |  | 
 | 	# Verify that events were reported using on-disk spellings of the | 
 | 	# directories and files that we touched.  We may or may not get a | 
 | 	# trailing slash on modified directories. | 
 | 	# | 
 | 	grep -E "^event: abc/?$"       ./insensitive.trace && | 
 | 	grep -E "^event: abc/def/?$"   ./insensitive.trace && | 
 | 	grep -E "^event: abc/def/xyz$" ./insensitive.trace | 
 | ' | 
 |  | 
 | # The variable "unicode_debug" is defined in the following library | 
 | # script to dump information about how the (OS, FS) handles Unicode | 
 | # composition.  Uncomment the following line if you want to enable it. | 
 | # | 
 | # unicode_debug=true | 
 |  | 
 | . "$TEST_DIRECTORY/lib-unicode-nfc-nfd.sh" | 
 |  | 
 | # See if the OS or filesystem does NFC/NFD aliasing/munging. | 
 | # | 
 | # The daemon should err on the side of caution and send BOTH the | 
 | # NFC and NFD forms.  It does not know the original spelling of | 
 | # the pathname (how the user thinks it should be spelled), so | 
 | # emit both and let the client decide (when necessary).  This is | 
 | # similar to "core.precomposeUnicode". | 
 | # | 
 | test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' | 
 | 	test_when_finished "stop_daemon_delete_repo test_unicode" && | 
 |  | 
 | 	git init test_unicode && | 
 |  | 
 | 	start_daemon -C test_unicode --tf "$PWD/unicode.trace" && | 
 |  | 
 | 	# Create a directory using an NFC spelling. | 
 | 	# | 
 | 	mkdir test_unicode/nfc && | 
 | 	mkdir test_unicode/nfc/c_${utf8_nfc} && | 
 |  | 
 | 	# Create a directory using an NFD spelling. | 
 | 	# | 
 | 	mkdir test_unicode/nfd && | 
 | 	mkdir test_unicode/nfd/d_${utf8_nfd} && | 
 |  | 
 | 	git -C test_unicode fsmonitor--daemon stop && | 
 |  | 
 | 	if test_have_prereq UNICODE_NFC_PRESERVED | 
 | 	then | 
 | 		# We should have seen NFC event from OS. | 
 | 		# We should not have synthesized an NFD event. | 
 | 		grep -E    "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && | 
 | 		grep -E -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace | 
 | 	else | 
 | 		# We should have seen NFD event from OS. | 
 | 		# We should have synthesized an NFC event. | 
 | 		grep -E "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && | 
 | 		grep -E "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace | 
 | 	fi && | 
 |  | 
 | 	# We assume UNICODE_NFD_PRESERVED. | 
 | 	# We should have seen explicit NFD from OS. | 
 | 	# We should have synthesized an NFC event. | 
 | 	grep -E "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && | 
 | 	grep -E "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace | 
 | ' | 
 |  | 
 | test_expect_success 'split-index and FSMonitor work well together' ' | 
 | 	git init split-index && | 
 | 	test_when_finished "git -C \"$PWD/split-index\" \ | 
 | 		fsmonitor--daemon stop" && | 
 | 	( | 
 | 		cd split-index && | 
 | 		git config core.splitIndex true && | 
 | 		# force split-index in most cases | 
 | 		git config splitIndex.maxPercentChange 99 && | 
 | 		git config core.fsmonitor true && | 
 |  | 
 | 		# Create the following commit topology: | 
 | 		# | 
 | 		# *   merge three | 
 | 		# |\ | 
 | 		# | * three | 
 | 		# * | merge two | 
 | 		# |\| | 
 | 		# | * two | 
 | 		# * | one | 
 | 		# |/ | 
 | 		# * 5a5efd7 initial | 
 |  | 
 | 		test_commit initial && | 
 | 		test_commit two && | 
 | 		test_commit three && | 
 | 		git reset --hard initial && | 
 | 		test_commit one && | 
 | 		test_tick && | 
 | 		git merge two && | 
 | 		test_tick && | 
 | 		git merge three && | 
 |  | 
 | 		git rebase --force-rebase -r one | 
 | 	) | 
 | ' | 
 |  | 
 | test_done |