1#!/bin/sh
2
3test_description='compare full workdir to sparse workdir'
4
5GIT_TEST_SPLIT_INDEX=0
6GIT_TEST_SPARSE_INDEX=
7
8. ./test-lib.sh
9
10test_expect_success 'setup' '
11	git init initial-repo &&
12	(
13		GIT_TEST_SPARSE_INDEX=0 &&
14		cd initial-repo &&
15		echo a >a &&
16		echo "after deep" >e &&
17		echo "after folder1" >g &&
18		echo "after x" >z &&
19		mkdir folder1 folder2 deep x &&
20		mkdir deep/deeper1 deep/deeper2 deep/before deep/later &&
21		mkdir deep/deeper1/deepest &&
22		echo "after deeper1" >deep/e &&
23		echo "after deepest" >deep/deeper1/e &&
24		cp a folder1 &&
25		cp a folder2 &&
26		cp a x &&
27		cp a deep &&
28		cp a deep/before &&
29		cp a deep/deeper1 &&
30		cp a deep/deeper2 &&
31		cp a deep/later &&
32		cp a deep/deeper1/deepest &&
33		cp -r deep/deeper1/deepest deep/deeper2 &&
34		mkdir deep/deeper1/0 &&
35		mkdir deep/deeper1/0/0 &&
36		touch deep/deeper1/0/1 &&
37		touch deep/deeper1/0/0/0 &&
38		>folder1- &&
39		>folder1.x &&
40		>folder10 &&
41		cp -r deep/deeper1/0 folder1 &&
42		cp -r deep/deeper1/0 folder2 &&
43		echo >>folder1/0/0/0 &&
44		echo >>folder2/0/1 &&
45		git add . &&
46		git commit -m "initial commit" &&
47		git checkout -b base &&
48		for dir in folder1 folder2 deep
49		do
50			git checkout -b update-$dir base &&
51			echo "updated $dir" >$dir/a &&
52			git commit -a -m "update $dir" || return 1
53		done &&
54
55		git checkout -b rename-base base &&
56		cat >folder1/larger-content <<-\EOF &&
57		matching
58		lines
59		help
60		inexact
61		renames
62		EOF
63		cp folder1/larger-content folder2/ &&
64		cp folder1/larger-content deep/deeper1/ &&
65		git add . &&
66		git commit -m "add interesting rename content" &&
67
68		git checkout -b rename-out-to-out rename-base &&
69		mv folder1/a folder2/b &&
70		mv folder1/larger-content folder2/edited-content &&
71		echo >>folder2/edited-content &&
72		echo >>folder2/0/1 &&
73		echo stuff >>deep/deeper1/a &&
74		git add . &&
75		git commit -m "rename folder1/... to folder2/..." &&
76
77		git checkout -b rename-out-to-in rename-base &&
78		mv folder1/a deep/deeper1/b &&
79		echo more stuff >>deep/deeper1/a &&
80		rm folder2/0/1 &&
81		mkdir folder2/0/1 &&
82		echo >>folder2/0/1/1 &&
83		mv folder1/larger-content deep/deeper1/edited-content &&
84		echo >>deep/deeper1/edited-content &&
85		git add . &&
86		git commit -m "rename folder1/... to deep/deeper1/..." &&
87
88		git checkout -b rename-in-to-out rename-base &&
89		mv deep/deeper1/a folder1/b &&
90		echo >>folder2/0/1 &&
91		rm -rf folder1/0/0 &&
92		echo >>folder1/0/0 &&
93		mv deep/deeper1/larger-content folder1/edited-content &&
94		echo >>folder1/edited-content &&
95		git add . &&
96		git commit -m "rename deep/deeper1/... to folder1/..." &&
97
98		git checkout -b df-conflict-1 base &&
99		rm -rf folder1 &&
100		echo content >folder1 &&
101		git add . &&
102		git commit -m "dir to file" &&
103
104		git checkout -b df-conflict-2 base &&
105		rm -rf folder2 &&
106		echo content >folder2 &&
107		git add . &&
108		git commit -m "dir to file" &&
109
110		git checkout -b fd-conflict base &&
111		rm a &&
112		mkdir a &&
113		echo content >a/a &&
114		git add . &&
115		git commit -m "file to dir" &&
116
117		for side in left right
118		do
119			git checkout -b merge-$side base &&
120			echo $side >>deep/deeper2/a &&
121			echo $side >>folder1/a &&
122			echo $side >>folder2/a &&
123			git add . &&
124			git commit -m "$side" || return 1
125		done &&
126
127		git checkout -b deepest base &&
128		echo "updated deepest" >deep/deeper1/deepest/a &&
129		git commit -a -m "update deepest" &&
130
131		git checkout -f base &&
132		git reset --hard
133	)
134'
135
136init_repos () {
137	rm -rf full-checkout sparse-checkout sparse-index &&
138
139	# create repos in initial state
140	cp -r initial-repo full-checkout &&
141	git -C full-checkout reset --hard &&
142
143	cp -r initial-repo sparse-checkout &&
144	git -C sparse-checkout reset --hard &&
145
146	cp -r initial-repo sparse-index &&
147	git -C sparse-index reset --hard &&
148
149	# initialize sparse-checkout definitions
150	git -C sparse-checkout sparse-checkout init --cone &&
151	git -C sparse-checkout sparse-checkout set deep &&
152	git -C sparse-index sparse-checkout init --cone --sparse-index &&
153	test_cmp_config -C sparse-index true index.sparse &&
154	git -C sparse-index sparse-checkout set deep
155}
156
157run_on_sparse () {
158	(
159		cd sparse-checkout &&
160		GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
161	) &&
162	(
163		cd sparse-index &&
164		GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
165	)
166}
167
168run_on_all () {
169	(
170		cd full-checkout &&
171		GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
172	) &&
173	run_on_sparse "$@"
174}
175
176test_all_match () {
177	run_on_all "$@" &&
178	test_cmp full-checkout-out sparse-checkout-out &&
179	test_cmp full-checkout-out sparse-index-out &&
180	test_cmp full-checkout-err sparse-checkout-err &&
181	test_cmp full-checkout-err sparse-index-err
182}
183
184test_sparse_match () {
185	run_on_sparse "$@" &&
186	test_cmp sparse-checkout-out sparse-index-out &&
187	test_cmp sparse-checkout-err sparse-index-err
188}
189
190test_sparse_unstaged () {
191	file=$1 &&
192	for repo in sparse-checkout sparse-index
193	do
194		# Skip "unmerged" paths
195		git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
196		test_must_be_empty diff || return 1
197	done
198}
199
200test_expect_success 'sparse-index contents' '
201	init_repos &&
202
203	test-tool -C sparse-index read-cache --table >cache &&
204	for dir in folder1 folder2 x
205	do
206		TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
207		grep "040000 tree $TREE	$dir/" cache \
208			|| return 1
209	done &&
210
211	git -C sparse-index sparse-checkout set folder1 &&
212
213	test-tool -C sparse-index read-cache --table >cache &&
214	for dir in deep folder2 x
215	do
216		TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
217		grep "040000 tree $TREE	$dir/" cache \
218			|| return 1
219	done &&
220
221	git -C sparse-index sparse-checkout set deep/deeper1 &&
222
223	test-tool -C sparse-index read-cache --table >cache &&
224	for dir in deep/deeper2 folder1 folder2 x
225	do
226		TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
227		grep "040000 tree $TREE	$dir/" cache \
228			|| return 1
229	done &&
230
231	# Disabling the sparse-index removes tree entries with full ones
232	git -C sparse-index sparse-checkout init --no-sparse-index &&
233
234	test-tool -C sparse-index read-cache --table >cache &&
235	! grep "040000 tree" cache &&
236	test_sparse_match test-tool read-cache --table
237'
238
239test_expect_success 'expanded in-memory index matches full index' '
240	init_repos &&
241	test_sparse_match test-tool read-cache --expand --table
242'
243
244test_expect_success 'status with options' '
245	init_repos &&
246	test_sparse_match ls &&
247	test_all_match git status --porcelain=v2 &&
248	test_all_match git status --porcelain=v2 -z -u &&
249	test_all_match git status --porcelain=v2 -uno &&
250	run_on_all touch README.md &&
251	test_all_match git status --porcelain=v2 &&
252	test_all_match git status --porcelain=v2 -z -u &&
253	test_all_match git status --porcelain=v2 -uno &&
254	test_all_match git add README.md &&
255	test_all_match git status --porcelain=v2 &&
256	test_all_match git status --porcelain=v2 -z -u &&
257	test_all_match git status --porcelain=v2 -uno
258'
259
260test_expect_success 'status reports sparse-checkout' '
261	init_repos &&
262	git -C sparse-checkout status >full &&
263	git -C sparse-index status >sparse &&
264	test_i18ngrep "You are in a sparse checkout with " full &&
265	test_i18ngrep "You are in a sparse checkout." sparse
266'
267
268test_expect_success 'add, commit, checkout' '
269	init_repos &&
270
271	write_script edit-contents <<-\EOF &&
272	echo text >>$1
273	EOF
274	run_on_all ../edit-contents README.md &&
275
276	test_all_match git add README.md &&
277	test_all_match git status --porcelain=v2 &&
278	test_all_match git commit -m "Add README.md" &&
279
280	test_all_match git checkout HEAD~1 &&
281	test_all_match git checkout - &&
282
283	run_on_all ../edit-contents README.md &&
284
285	test_all_match git add -A &&
286	test_all_match git status --porcelain=v2 &&
287	test_all_match git commit -m "Extend README.md" &&
288
289	test_all_match git checkout HEAD~1 &&
290	test_all_match git checkout - &&
291
292	run_on_all ../edit-contents deep/newfile &&
293
294	test_all_match git status --porcelain=v2 -uno &&
295	test_all_match git status --porcelain=v2 &&
296	test_all_match git add . &&
297	test_all_match git status --porcelain=v2 &&
298	test_all_match git commit -m "add deep/newfile" &&
299
300	test_all_match git checkout HEAD~1 &&
301	test_all_match git checkout -
302'
303
304test_expect_success 'add outside sparse cone' '
305	init_repos &&
306
307	run_on_sparse mkdir folder1 &&
308	run_on_sparse ../edit-contents folder1/a &&
309	run_on_sparse ../edit-contents folder1/newfile &&
310	test_sparse_match test_must_fail git add folder1/a &&
311	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
312	test_sparse_unstaged folder1/a &&
313	test_sparse_match test_must_fail git add folder1/newfile &&
314	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
315	test_sparse_unstaged folder1/newfile
316'
317
318test_expect_success 'commit including unstaged changes' '
319	init_repos &&
320
321	write_script edit-file <<-\EOF &&
322	echo $1 >$2
323	EOF
324
325	run_on_all ../edit-file 1 a &&
326	run_on_all ../edit-file 1 deep/a &&
327
328	test_all_match git commit -m "-a" -a &&
329	test_all_match git status --porcelain=v2 &&
330
331	run_on_all ../edit-file 2 a &&
332	run_on_all ../edit-file 2 deep/a &&
333
334	test_all_match git commit -m "--include" --include deep/a &&
335	test_all_match git status --porcelain=v2 &&
336	test_all_match git commit -m "--include" --include a &&
337	test_all_match git status --porcelain=v2 &&
338
339	run_on_all ../edit-file 3 a &&
340	run_on_all ../edit-file 3 deep/a &&
341
342	test_all_match git commit -m "--amend" -a --amend &&
343	test_all_match git status --porcelain=v2
344'
345
346test_expect_success 'status/add: outside sparse cone' '
347	init_repos &&
348
349	# folder1 is at HEAD, but outside the sparse cone
350	run_on_sparse mkdir folder1 &&
351	cp initial-repo/folder1/a sparse-checkout/folder1/a &&
352	cp initial-repo/folder1/a sparse-index/folder1/a &&
353
354	test_sparse_match git status &&
355
356	write_script edit-contents <<-\EOF &&
357	echo text >>$1
358	EOF
359	run_on_sparse ../edit-contents folder1/a &&
360	run_on_all ../edit-contents folder1/new &&
361
362	test_sparse_match git status --porcelain=v2 &&
363
364	# Adding the path outside of the sparse-checkout cone should fail.
365	test_sparse_match test_must_fail git add folder1/a &&
366	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
367	test_sparse_unstaged folder1/a &&
368	test_sparse_match test_must_fail git add --refresh folder1/a &&
369	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
370	test_sparse_unstaged folder1/a &&
371	test_sparse_match test_must_fail git add folder1/new &&
372	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
373	test_sparse_unstaged folder1/new &&
374	test_sparse_match git add --sparse folder1/a &&
375	test_sparse_match git add --sparse folder1/new &&
376
377	test_all_match git add --sparse . &&
378	test_all_match git status --porcelain=v2 &&
379	test_all_match git commit -m folder1/new &&
380	test_all_match git rev-parse HEAD^{tree} &&
381
382	run_on_all ../edit-contents folder1/newer &&
383	test_all_match git add --sparse folder1/ &&
384	test_all_match git status --porcelain=v2 &&
385	test_all_match git commit -m folder1/newer &&
386	test_all_match git rev-parse HEAD^{tree}
387'
388
389test_expect_success 'checkout and reset --hard' '
390	init_repos &&
391
392	test_all_match git checkout update-folder1 &&
393	test_all_match git status --porcelain=v2 &&
394
395	test_all_match git checkout update-deep &&
396	test_all_match git status --porcelain=v2 &&
397
398	test_all_match git checkout -b reset-test &&
399	test_all_match git reset --hard deepest &&
400	test_all_match git reset --hard update-folder1 &&
401	test_all_match git reset --hard update-folder2
402'
403
404test_expect_success 'diff --staged' '
405	init_repos &&
406
407	write_script edit-contents <<-\EOF &&
408	echo text >>README.md
409	EOF
410	run_on_all ../edit-contents &&
411
412	test_all_match git diff &&
413	test_all_match git diff --staged &&
414	test_all_match git add README.md &&
415	test_all_match git diff &&
416	test_all_match git diff --staged
417'
418
419# NEEDSWORK: sparse-checkout behaves differently from full-checkout when
420# running this test with 'df-conflict-2' after 'df-conflict-1'.
421test_expect_success 'diff with renames and conflicts' '
422	init_repos &&
423
424	for branch in rename-out-to-out \
425		      rename-out-to-in \
426		      rename-in-to-out \
427		      df-conflict-1 \
428		      fd-conflict
429	do
430		test_all_match git checkout rename-base &&
431		test_all_match git checkout $branch -- . &&
432		test_all_match git status --porcelain=v2 &&
433		test_all_match git diff --staged --no-renames &&
434		test_all_match git diff --staged --find-renames || return 1
435	done
436'
437
438test_expect_success 'diff with directory/file conflicts' '
439	init_repos &&
440
441	for branch in rename-out-to-out \
442		      rename-out-to-in \
443		      rename-in-to-out \
444		      df-conflict-1 \
445		      df-conflict-2 \
446		      fd-conflict
447	do
448		git -C full-checkout reset --hard &&
449		test_sparse_match git reset --hard &&
450		test_all_match git checkout $branch &&
451		test_all_match git checkout rename-base -- . &&
452		test_all_match git status --porcelain=v2 &&
453		test_all_match git diff --staged --no-renames &&
454		test_all_match git diff --staged --find-renames || return 1
455	done
456'
457
458test_expect_success 'log with pathspec outside sparse definition' '
459	init_repos &&
460
461	test_all_match git log -- a &&
462	test_all_match git log -- folder1/a &&
463	test_all_match git log -- folder2/a &&
464	test_all_match git log -- deep/a &&
465	test_all_match git log -- deep/deeper1/a &&
466	test_all_match git log -- deep/deeper1/deepest/a &&
467
468	test_all_match git checkout update-folder1 &&
469	test_all_match git log -- folder1/a
470'
471
472test_expect_success 'blame with pathspec inside sparse definition' '
473	init_repos &&
474
475	test_all_match git blame a &&
476	test_all_match git blame deep/a &&
477	test_all_match git blame deep/deeper1/a &&
478	test_all_match git blame deep/deeper1/deepest/a
479'
480
481# TODO: blame currently does not support blaming files outside of the
482# sparse definition. It complains that the file doesn't exist locally.
483test_expect_failure 'blame with pathspec outside sparse definition' '
484	init_repos &&
485
486	test_all_match git blame folder1/a &&
487	test_all_match git blame folder2/a &&
488	test_all_match git blame deep/deeper2/a &&
489	test_all_match git blame deep/deeper2/deepest/a
490'
491
492# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
493# in this scenario, but it shouldn't.
494test_expect_failure 'checkout and reset (mixed)' '
495	init_repos &&
496
497	test_all_match git checkout -b reset-test update-deep &&
498	test_all_match git reset deepest &&
499	test_all_match git reset update-folder1 &&
500	test_all_match git reset update-folder2
501'
502
503# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
504# in this scenario, but it shouldn't.
505test_expect_success 'checkout and reset (mixed) [sparse]' '
506	init_repos &&
507
508	test_sparse_match git checkout -b reset-test update-deep &&
509	test_sparse_match git reset deepest &&
510	test_sparse_match git reset update-folder1 &&
511	test_sparse_match git reset update-folder2
512'
513
514test_expect_success 'merge, cherry-pick, and rebase' '
515	init_repos &&
516
517	for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
518	do
519		test_all_match git checkout -B temp update-deep &&
520		test_all_match git $OPERATION update-folder1 &&
521		test_all_match git rev-parse HEAD^{tree} &&
522		test_all_match git $OPERATION update-folder2 &&
523		test_all_match git rev-parse HEAD^{tree} || return 1
524	done
525'
526
527test_expect_success 'merge with conflict outside cone' '
528	init_repos &&
529
530	test_all_match git checkout -b merge-tip merge-left &&
531	test_all_match git status --porcelain=v2 &&
532	test_all_match test_must_fail git merge -m merge merge-right &&
533	test_all_match git status --porcelain=v2 &&
534
535	# Resolve the conflict in different ways:
536	# 1. Revert to the base
537	test_all_match git checkout base -- deep/deeper2/a &&
538	test_all_match git status --porcelain=v2 &&
539
540	# 2. Add the file with conflict markers
541	test_sparse_match test_must_fail git add folder1/a &&
542	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
543	test_sparse_unstaged folder1/a &&
544	test_all_match git add --sparse folder1/a &&
545	test_all_match git status --porcelain=v2 &&
546
547	# 3. Rename the file to another sparse filename and
548	#    accept conflict markers as resolved content.
549	run_on_all mv folder2/a folder2/z &&
550	test_sparse_match test_must_fail git add folder2 &&
551	grep "Disable or modify the sparsity rules" sparse-checkout-err &&
552	test_sparse_unstaged folder2/z &&
553	test_all_match git add --sparse folder2 &&
554	test_all_match git status --porcelain=v2 &&
555
556	test_all_match git merge --continue &&
557	test_all_match git status --porcelain=v2 &&
558	test_all_match git rev-parse HEAD^{tree}
559'
560
561test_expect_success 'cherry-pick/rebase with conflict outside cone' '
562	init_repos &&
563
564	for OPERATION in cherry-pick rebase
565	do
566		test_all_match git checkout -B tip &&
567		test_all_match git reset --hard merge-left &&
568		test_all_match git status --porcelain=v2 &&
569		test_all_match test_must_fail git $OPERATION merge-right &&
570		test_all_match git status --porcelain=v2 &&
571
572		# Resolve the conflict in different ways:
573		# 1. Revert to the base
574		test_all_match git checkout base -- deep/deeper2/a &&
575		test_all_match git status --porcelain=v2 &&
576
577		# 2. Add the file with conflict markers
578		# NEEDSWORK: Even though the merge conflict removed the
579		# SKIP_WORKTREE bit from the index entry for folder1/a, we should
580		# warn that this is a problematic add.
581		test_sparse_match test_must_fail git add folder1/a &&
582		grep "Disable or modify the sparsity rules" sparse-checkout-err &&
583		test_sparse_unstaged folder1/a &&
584		test_all_match git add --sparse folder1/a &&
585		test_all_match git status --porcelain=v2 &&
586
587		# 3. Rename the file to another sparse filename and
588		#    accept conflict markers as resolved content.
589		# NEEDSWORK: This mode now fails, because folder2/z is
590		# outside of the sparse-checkout cone and does not match an
591		# existing index entry with the SKIP_WORKTREE bit cleared.
592		run_on_all mv folder2/a folder2/z &&
593		test_sparse_match test_must_fail git add folder2 &&
594		grep "Disable or modify the sparsity rules" sparse-checkout-err &&
595		test_sparse_unstaged folder2/z &&
596		test_all_match git add --sparse folder2 &&
597		test_all_match git status --porcelain=v2 &&
598
599		test_all_match git $OPERATION --continue &&
600		test_all_match git status --porcelain=v2 &&
601		test_all_match git rev-parse HEAD^{tree} || return 1
602	done
603'
604
605test_expect_success 'merge with outside renames' '
606	init_repos &&
607
608	for type in out-to-out out-to-in in-to-out
609	do
610		test_all_match git reset --hard &&
611		test_all_match git checkout -f -b merge-$type update-deep &&
612		test_all_match git merge -m "$type" rename-$type &&
613		test_all_match git rev-parse HEAD^{tree} || return 1
614	done
615'
616
617# Sparse-index fails to convert the index in the
618# final 'git cherry-pick' command.
619test_expect_success 'cherry-pick with conflicts' '
620	init_repos &&
621
622	write_script edit-conflict <<-\EOF &&
623	echo $1 >conflict
624	EOF
625
626	test_all_match git checkout -b to-cherry-pick &&
627	run_on_all ../edit-conflict ABC &&
628	test_all_match git add conflict &&
629	test_all_match git commit -m "conflict to pick" &&
630
631	test_all_match git checkout -B base HEAD~1 &&
632	run_on_all ../edit-conflict DEF &&
633	test_all_match git add conflict &&
634	test_all_match git commit -m "conflict in base" &&
635
636	test_all_match test_must_fail git cherry-pick to-cherry-pick
637'
638
639test_expect_success 'clean' '
640	init_repos &&
641
642	echo bogus >>.gitignore &&
643	run_on_all cp ../.gitignore . &&
644	test_all_match git add .gitignore &&
645	test_all_match git commit -m "ignore bogus files" &&
646
647	run_on_sparse mkdir folder1 &&
648	run_on_all touch folder1/bogus &&
649
650	test_all_match git status --porcelain=v2 &&
651	test_all_match git clean -f &&
652	test_all_match git status --porcelain=v2 &&
653	test_sparse_match ls &&
654	test_sparse_match ls folder1 &&
655
656	test_all_match git clean -xf &&
657	test_all_match git status --porcelain=v2 &&
658	test_sparse_match ls &&
659	test_sparse_match ls folder1 &&
660
661	test_all_match git clean -xdf &&
662	test_all_match git status --porcelain=v2 &&
663	test_sparse_match ls &&
664	test_sparse_match ls folder1 &&
665
666	test_sparse_match test_path_is_dir folder1
667'
668
669test_expect_success 'submodule handling' '
670	init_repos &&
671
672	test_sparse_match git sparse-checkout add modules &&
673	test_all_match mkdir modules &&
674	test_all_match touch modules/a &&
675	test_all_match git add modules &&
676	test_all_match git commit -m "add modules directory" &&
677
678	run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
679	test_all_match git commit -m "add submodule" &&
680
681	# having a submodule prevents "modules" from collapse
682	test_sparse_match git sparse-checkout set deep/deeper1 &&
683	test-tool -C sparse-index read-cache --table >cache &&
684	grep "100644 blob .*	modules/a" cache &&
685	grep "160000 commit $(git -C initial-repo rev-parse HEAD)	modules/sub" cache
686'
687
688test_expect_success 'sparse-index is expanded and converted back' '
689	init_repos &&
690
691	GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
692		git -C sparse-index -c core.fsmonitor="" reset --hard &&
693	test_region index convert_to_sparse trace2.txt &&
694	test_region index ensure_full_index trace2.txt
695'
696
697ensure_not_expanded () {
698	rm -f trace2.txt &&
699	echo >>sparse-index/untracked.txt &&
700
701	if test "$1" = "!"
702	then
703		shift &&
704		test_must_fail env \
705			GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
706			git -C sparse-index "$@" || return 1
707	else
708		GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
709			git -C sparse-index "$@" || return 1
710	fi &&
711	test_region ! index ensure_full_index trace2.txt
712}
713
714test_expect_success 'sparse-index is not expanded' '
715	init_repos &&
716
717	ensure_not_expanded status &&
718	ensure_not_expanded commit --allow-empty -m empty &&
719	echo >>sparse-index/a &&
720	ensure_not_expanded commit -a -m a &&
721	echo >>sparse-index/a &&
722	ensure_not_expanded commit --include a -m a &&
723	echo >>sparse-index/deep/deeper1/a &&
724	ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
725	ensure_not_expanded checkout rename-out-to-out &&
726	ensure_not_expanded checkout - &&
727	ensure_not_expanded switch rename-out-to-out &&
728	ensure_not_expanded switch - &&
729	git -C sparse-index reset --hard &&
730	ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
731	git -C sparse-index reset --hard &&
732	ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
733
734	echo >>sparse-index/README.md &&
735	ensure_not_expanded add -A &&
736	echo >>sparse-index/extra.txt &&
737	ensure_not_expanded add extra.txt &&
738	echo >>sparse-index/untracked.txt &&
739	ensure_not_expanded add . &&
740
741	ensure_not_expanded checkout -f update-deep &&
742	test_config -C sparse-index pull.twohead ort &&
743	(
744		sane_unset GIT_TEST_MERGE_ALGORITHM &&
745		for OPERATION in "merge -m merge" cherry-pick rebase
746		do
747			ensure_not_expanded merge -m merge update-folder1 &&
748			ensure_not_expanded merge -m merge update-folder2 || return 1
749		done
750	)
751'
752
753test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
754	init_repos &&
755
756	for side in right left
757	do
758		git -C sparse-index checkout -b expand-$side base &&
759		echo $side >sparse-index/deep/a &&
760		git -C sparse-index commit -a -m "$side" || return 1
761	done &&
762
763	(
764		sane_unset GIT_TEST_MERGE_ALGORITHM &&
765		git -C sparse-index config pull.twohead ort &&
766		ensure_not_expanded ! merge -m merged expand-right
767	)
768'
769
770# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
771# in this scenario, but it shouldn't.
772test_expect_success 'reset mixed and checkout orphan' '
773	init_repos &&
774
775	test_all_match git checkout rename-out-to-in &&
776
777	# Sparse checkouts do not agree with full checkouts about
778	# how to report a directory/file conflict during a reset.
779	# This command would fail with test_all_match because the
780	# full checkout reports "T folder1/0/1" while a sparse
781	# checkout reports "D folder1/0/1". This matches because
782	# the sparse checkouts skip "adding" the other side of
783	# the conflict.
784	test_sparse_match git reset --mixed HEAD~1 &&
785	test_sparse_match test-tool read-cache --table --expand &&
786	test_sparse_match git status --porcelain=v2 &&
787
788	# At this point, sparse-checkouts behave differently
789	# from the full-checkout.
790	test_sparse_match git checkout --orphan new-branch &&
791	test_sparse_match test-tool read-cache --table --expand &&
792	test_sparse_match git status --porcelain=v2
793'
794
795test_expect_success 'add everything with deep new file' '
796	init_repos &&
797
798	run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
799
800	run_on_all touch deep/deeper1/x &&
801	test_all_match git add . &&
802	test_all_match git status --porcelain=v2
803'
804
805# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
806# directory/file conflicts, even without sparse-checkout. Use this
807# test only as a documentation of the incorrect behavior, not a
808# measure of how it _should_ behave.
809test_expect_success 'checkout behaves oddly with df-conflict-1' '
810	init_repos &&
811
812	test_sparse_match git sparse-checkout disable &&
813
814	write_script edit-content <<-\EOF &&
815	echo content >>folder1/larger-content
816	git add folder1
817	EOF
818
819	run_on_all ../edit-content &&
820	test_all_match git status --porcelain=v2 &&
821
822	git -C sparse-checkout sparse-checkout init --cone &&
823	git -C sparse-index sparse-checkout init --cone --sparse-index &&
824
825	test_all_match git status --porcelain=v2 &&
826
827	# This checkout command should fail, because we have a staged
828	# change to folder1/larger-content, but the destination changes
829	# folder1 to a file.
830	git -C full-checkout checkout df-conflict-1 \
831		1>full-checkout-out \
832		2>full-checkout-err &&
833	git -C sparse-checkout checkout df-conflict-1 \
834		1>sparse-checkout-out \
835		2>sparse-checkout-err &&
836	git -C sparse-index checkout df-conflict-1 \
837		1>sparse-index-out \
838		2>sparse-index-err &&
839
840	# Instead, the checkout deletes the folder1 file and adds the
841	# folder1/larger-content file, leaving all other paths that were
842	# in folder1/ as deleted (without any warning).
843	cat >expect <<-EOF &&
844	D	folder1
845	A	folder1/larger-content
846	EOF
847	test_cmp expect full-checkout-out &&
848	test_cmp expect sparse-checkout-out &&
849
850	# The sparse-index reports no output
851	test_must_be_empty sparse-index-out &&
852
853	# stderr: Switched to branch df-conflict-1
854	test_cmp full-checkout-err sparse-checkout-err &&
855	test_cmp full-checkout-err sparse-checkout-err
856'
857
858# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
859# directory/file conflicts, even without sparse-checkout. Use this
860# test only as a documentation of the incorrect behavior, not a
861# measure of how it _should_ behave.
862test_expect_success 'checkout behaves oddly with df-conflict-2' '
863	init_repos &&
864
865	test_sparse_match git sparse-checkout disable &&
866
867	write_script edit-content <<-\EOF &&
868	echo content >>folder2/larger-content
869	git add folder2
870	EOF
871
872	run_on_all ../edit-content &&
873	test_all_match git status --porcelain=v2 &&
874
875	git -C sparse-checkout sparse-checkout init --cone &&
876	git -C sparse-index sparse-checkout init --cone --sparse-index &&
877
878	test_all_match git status --porcelain=v2 &&
879
880	# This checkout command should fail, because we have a staged
881	# change to folder1/larger-content, but the destination changes
882	# folder1 to a file.
883	git -C full-checkout checkout df-conflict-2 \
884		1>full-checkout-out \
885		2>full-checkout-err &&
886	git -C sparse-checkout checkout df-conflict-2 \
887		1>sparse-checkout-out \
888		2>sparse-checkout-err &&
889	git -C sparse-index checkout df-conflict-2 \
890		1>sparse-index-out \
891		2>sparse-index-err &&
892
893	# The full checkout deviates from the df-conflict-1 case here!
894	# It drops the change to folder1/larger-content and leaves the
895	# folder1 path as-is on disk. The sparse-index behaves the same.
896	test_must_be_empty full-checkout-out &&
897	test_must_be_empty sparse-index-out &&
898
899	# In the sparse-checkout case, the checkout deletes the folder1
900	# file and adds the folder1/larger-content file, leaving all other
901	# paths that were in folder1/ as deleted (without any warning).
902	cat >expect <<-EOF &&
903	D	folder2
904	A	folder2/larger-content
905	EOF
906	test_cmp expect sparse-checkout-out &&
907
908	# Switched to branch df-conflict-1
909	test_cmp full-checkout-err sparse-checkout-err &&
910	test_cmp full-checkout-err sparse-index-err
911'
912
913test_done
914