1#!/bin/sh
2
3test_description='git status with file system watcher'
4
5. ./test-lib.sh
6
7# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE'
8# "git update-index --fsmonitor" can be used to get the extension written
9# before testing the results.
10
11clean_repo () {
12	git reset --hard HEAD &&
13	git clean -fd
14}
15
16dirty_repo () {
17	: >untracked &&
18	: >dir1/untracked &&
19	: >dir2/untracked &&
20	echo 1 >modified &&
21	echo 2 >dir1/modified &&
22	echo 3 >dir2/modified &&
23	echo 4 >new &&
24	echo 5 >dir1/new &&
25	echo 6 >dir2/new
26}
27
28write_integration_script () {
29	write_script .git/hooks/fsmonitor-test<<-\EOF
30	if test "$#" -ne 2
31	then
32		echo "$0: exactly 2 arguments expected"
33		exit 2
34	fi
35	if test "$1" != 2
36	then
37		echo "Unsupported core.fsmonitor hook version." >&2
38		exit 1
39	fi
40	printf "last_update_token\0"
41	printf "untracked\0"
42	printf "dir1/untracked\0"
43	printf "dir2/untracked\0"
44	printf "modified\0"
45	printf "dir1/modified\0"
46	printf "dir2/modified\0"
47	printf "new\0"
48	printf "dir1/new\0"
49	printf "dir2/new\0"
50	EOF
51}
52
53test_lazy_prereq UNTRACKED_CACHE '
54	{ git update-index --test-untracked-cache; ret=$?; } &&
55	test $ret -ne 1
56'
57
58test_expect_success 'setup' '
59	mkdir -p .git/hooks &&
60	: >tracked &&
61	: >modified &&
62	mkdir dir1 &&
63	: >dir1/tracked &&
64	: >dir1/modified &&
65	mkdir dir2 &&
66	: >dir2/tracked &&
67	: >dir2/modified &&
68	git -c core.fsmonitor= add . &&
69	git -c core.fsmonitor= commit -m initial &&
70	git config core.fsmonitor .git/hooks/fsmonitor-test &&
71	cat >.gitignore <<-\EOF
72	.gitignore
73	expect*
74	actual*
75	marker*
76	trace2*
77	EOF
78'
79
80# test that the fsmonitor extension is off by default
81test_expect_success 'fsmonitor extension is off by default' '
82	test-tool dump-fsmonitor >actual &&
83	grep "^no fsmonitor" actual
84'
85
86# test that "update-index --fsmonitor" adds the fsmonitor extension
87test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' '
88	git update-index --fsmonitor &&
89	test-tool dump-fsmonitor >actual &&
90	grep "^fsmonitor last update" actual
91'
92
93# test that "update-index --no-fsmonitor" removes the fsmonitor extension
94test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' '
95	git update-index --no-fsmonitor &&
96	test-tool dump-fsmonitor >actual &&
97	grep "^no fsmonitor" actual
98'
99
100cat >expect <<EOF &&
101h dir1/modified
102H dir1/tracked
103h dir2/modified
104H dir2/tracked
105h modified
106H tracked
107EOF
108
109# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
110test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
111	write_script .git/hooks/fsmonitor-test<<-\EOF &&
112		printf "last_update_token\0"
113	EOF
114	git update-index --fsmonitor &&
115	git update-index --fsmonitor-valid dir1/modified &&
116	git update-index --fsmonitor-valid dir2/modified &&
117	git update-index --fsmonitor-valid modified &&
118	git ls-files -f >actual &&
119	test_cmp expect actual
120'
121
122cat >expect <<EOF &&
123H dir1/modified
124H dir1/tracked
125H dir2/modified
126H dir2/tracked
127H modified
128H tracked
129EOF
130
131# test that "update-index --no-fsmonitor-valid" clears the fsmonitor valid bit
132test_expect_success 'update-index --no-fsmonitor-valid" clears the fsmonitor valid bit' '
133	git update-index --no-fsmonitor-valid dir1/modified &&
134	git update-index --no-fsmonitor-valid dir2/modified &&
135	git update-index --no-fsmonitor-valid modified &&
136	git ls-files -f >actual &&
137	test_cmp expect actual
138'
139
140cat >expect <<EOF &&
141H dir1/modified
142H dir1/tracked
143H dir2/modified
144H dir2/tracked
145H modified
146H tracked
147EOF
148
149# test that all files returned by the script get flagged as invalid
150test_expect_success 'all files returned by integration script get flagged as invalid' '
151	write_integration_script &&
152	dirty_repo &&
153	git update-index --fsmonitor &&
154	git ls-files -f >actual &&
155	test_cmp expect actual
156'
157
158cat >expect <<EOF &&
159H dir1/modified
160h dir1/new
161H dir1/tracked
162H dir2/modified
163h dir2/new
164H dir2/tracked
165H modified
166h new
167H tracked
168EOF
169
170# test that newly added files are marked valid
171test_expect_success 'newly added files are marked valid' '
172	write_script .git/hooks/fsmonitor-test<<-\EOF &&
173		printf "last_update_token\0"
174	EOF
175	git add new &&
176	git add dir1/new &&
177	git add dir2/new &&
178	git ls-files -f >actual &&
179	test_cmp expect actual
180'
181
182cat >expect <<EOF &&
183H dir1/modified
184h dir1/new
185h dir1/tracked
186H dir2/modified
187h dir2/new
188h dir2/tracked
189H modified
190h new
191h tracked
192EOF
193
194# test that all unmodified files get marked valid
195test_expect_success 'all unmodified files get marked valid' '
196	# modified files result in update-index returning 1
197	test_must_fail git update-index --refresh --force-write-index &&
198	git ls-files -f >actual &&
199	test_cmp expect actual
200'
201
202cat >expect <<EOF &&
203H dir1/modified
204h dir1/tracked
205h dir2/modified
206h dir2/tracked
207h modified
208h tracked
209EOF
210
211# test that *only* files returned by the integration script get flagged as invalid
212test_expect_success '*only* files returned by the integration script get flagged as invalid' '
213	write_script .git/hooks/fsmonitor-test<<-\EOF &&
214	printf "last_update_token\0"
215	printf "dir1/modified\0"
216	EOF
217	clean_repo &&
218	git update-index --refresh --force-write-index &&
219	echo 1 >modified &&
220	echo 2 >dir1/modified &&
221	echo 3 >dir2/modified &&
222	test_must_fail git update-index --refresh --force-write-index &&
223	git ls-files -f >actual &&
224	test_cmp expect actual
225'
226
227# Ensure commands that call refresh_index() to move the index back in time
228# properly invalidate the fsmonitor cache
229test_expect_success 'refresh_index() invalidates fsmonitor cache' '
230	clean_repo &&
231	dirty_repo &&
232	write_integration_script &&
233	git add . &&
234	write_script .git/hooks/fsmonitor-test<<-\EOF &&
235	EOF
236	git commit -m "to reset" &&
237	git reset HEAD~1 &&
238	git status >actual &&
239	git -c core.fsmonitor= status >expect &&
240	test_cmp expect actual
241'
242
243# test fsmonitor with and without preloadIndex
244preload_values="false true"
245for preload_val in $preload_values
246do
247	test_expect_success "setup preloadIndex to $preload_val" '
248		git config core.preloadIndex $preload_val &&
249		if test $preload_val = true
250		then
251			GIT_TEST_PRELOAD_INDEX=$preload_val; export GIT_TEST_PRELOAD_INDEX
252		else
253			sane_unset GIT_TEST_PRELOAD_INDEX
254		fi
255	'
256
257	# test fsmonitor with and without the untracked cache (if available)
258	uc_values="false"
259	test_have_prereq UNTRACKED_CACHE && uc_values="false true"
260	for uc_val in $uc_values
261	do
262		test_expect_success "setup untracked cache to $uc_val" '
263			git config core.untrackedcache $uc_val
264		'
265
266		# Status is well tested elsewhere so we'll just ensure that the results are
267		# the same when using core.fsmonitor.
268		test_expect_success 'compare status with and without fsmonitor' '
269			write_integration_script &&
270			clean_repo &&
271			dirty_repo &&
272			git add new &&
273			git add dir1/new &&
274			git add dir2/new &&
275			git status >actual &&
276			git -c core.fsmonitor= status >expect &&
277			test_cmp expect actual
278		'
279
280		# Make sure it's actually skipping the check for modified and untracked
281		# (if enabled) files unless it is told about them.
282		test_expect_success "status doesn't detect unreported modifications" '
283			write_script .git/hooks/fsmonitor-test<<-\EOF &&
284			printf "last_update_token\0"
285			:>marker
286			EOF
287			clean_repo &&
288			git status &&
289			test_path_is_file marker &&
290			dirty_repo &&
291			rm -f marker &&
292			git status >actual &&
293			test_path_is_file marker &&
294			test_i18ngrep ! "Changes not staged for commit:" actual &&
295			if test $uc_val = true
296			then
297				test_i18ngrep ! "Untracked files:" actual
298			fi &&
299			if test $uc_val = false
300			then
301				test_i18ngrep "Untracked files:" actual
302			fi &&
303			rm -f marker
304		'
305	done
306done
307
308# test that splitting the index doesn't interfere
309test_expect_success 'splitting the index results in the same state' '
310	write_integration_script &&
311	dirty_repo &&
312	git update-index --fsmonitor  &&
313	git ls-files -f >expect &&
314	test-tool dump-fsmonitor >&2 && echo &&
315	git update-index --fsmonitor --split-index &&
316	test-tool dump-fsmonitor >&2 && echo &&
317	git ls-files -f >actual &&
318	test_cmp expect actual
319'
320
321test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' '
322	test_create_repo dot-git &&
323	(
324		cd dot-git &&
325		mkdir -p .git/hooks &&
326		: >tracked &&
327		: >modified &&
328		mkdir dir1 &&
329		: >dir1/tracked &&
330		: >dir1/modified &&
331		mkdir dir2 &&
332		: >dir2/tracked &&
333		: >dir2/modified &&
334		write_integration_script &&
335		git config core.fsmonitor .git/hooks/fsmonitor-test &&
336		git update-index --untracked-cache &&
337		git update-index --fsmonitor &&
338		GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-before" \
339		git status &&
340		test-tool dump-untracked-cache >../before
341	) &&
342	cat >>dot-git/.git/hooks/fsmonitor-test <<-\EOF &&
343	printf ".git\0"
344	printf ".git/index\0"
345	printf "dir1/.git\0"
346	printf "dir1/.git/index\0"
347	EOF
348	(
349		cd dot-git &&
350		GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-after" \
351		git status &&
352		test-tool dump-untracked-cache >../after
353	) &&
354	grep "directory-invalidation" trace-before | cut -d"|" -f 9 >>before &&
355	grep "directory-invalidation" trace-after  | cut -d"|" -f 9 >>after &&
356	# UNTR extension unchanged, dir invalidation count unchanged
357	test_cmp before after
358'
359
360test_expect_success 'discard_index() also discards fsmonitor info' '
361	test_config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-all" &&
362	test_might_fail git update-index --refresh &&
363	test-tool read-cache --print-and-refresh=tracked 2 >actual &&
364	printf "tracked is%s up to date\n" "" " not" >expect &&
365	test_cmp expect actual
366'
367
368# Test unstaging entries that:
369#  - Are not flagged with CE_FSMONITOR_VALID
370#  - Have a position in the index >= the number of entries present in the index
371#    after unstaging.
372test_expect_success 'status succeeds after staging/unstaging' '
373	test_create_repo fsmonitor-stage-unstage &&
374	(
375		cd fsmonitor-stage-unstage &&
376		test_commit initial &&
377		git update-index --fsmonitor &&
378		removed=$(test_seq 1 100 | sed "s/^/z/") &&
379		touch $removed &&
380		git add $removed &&
381		git config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-env" &&
382		FSMONITOR_LIST="$removed" git restore -S $removed &&
383		FSMONITOR_LIST="$removed" git status
384	)
385'
386
387# Usage:
388# check_sparse_index_behavior [!]
389# If "!" is supplied, then we verify that we do not call ensure_full_index
390# during a call to 'git status'. Otherwise, we verify that we _do_ call it.
391check_sparse_index_behavior () {
392	git -C full status --porcelain=v2 >expect &&
393	GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
394		git -C sparse status --porcelain=v2 >actual &&
395	test_region $1 index ensure_full_index trace2.txt &&
396	test_region fsm_hook query trace2.txt &&
397	test_cmp expect actual &&
398	rm trace2.txt
399}
400
401test_expect_success 'status succeeds with sparse index' '
402	(
403		sane_unset GIT_TEST_SPLIT_INDEX &&
404
405		git clone . full &&
406		git clone --sparse . sparse &&
407		git -C sparse sparse-checkout init --cone --sparse-index &&
408		git -C sparse sparse-checkout set dir1 dir2 &&
409
410		write_script .git/hooks/fsmonitor-test <<-\EOF &&
411			printf "last_update_token\0"
412		EOF
413		git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test &&
414		git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test &&
415		check_sparse_index_behavior ! &&
416
417		write_script .git/hooks/fsmonitor-test <<-\EOF &&
418			printf "last_update_token\0"
419			printf "dir1/modified\0"
420		EOF
421		check_sparse_index_behavior ! &&
422
423		git -C sparse sparse-checkout add dir1a &&
424
425		for repo in full sparse
426		do
427			cp -r $repo/dir1 $repo/dir1a &&
428			git -C $repo add dir1a &&
429			git -C $repo commit -m "add dir1a" || return 1
430		done &&
431		git -C sparse sparse-checkout set dir1 dir2 &&
432
433		# This one modifies outside the sparse-checkout definition
434		# and hence we expect to expand the sparse-index.
435		write_script .git/hooks/fsmonitor-test <<-\EOF &&
436			printf "last_update_token\0"
437			printf "dir1a/modified\0"
438		EOF
439		check_sparse_index_behavior
440	)
441'
442
443test_done
444