#!/bin/sh

test_description='parallel-checkout basics

Ensure that parallel-checkout basically works on clone and checkout, spawning
the required number of workers and correctly populating both the index and the
working tree.
'

TEST_NO_CREATE_REPO=1
. ./test-lib.sh
. "$TEST_DIRECTORY/lib-parallel-checkout.sh"

# Test parallel-checkout with a branch switch containing a variety of file
# creations, deletions, and modifications, involving different entry types.
# The branches B1 and B2 have the following paths:
#
#      B1                 B2
#  a/a (file)         a   (file)
#  b   (file)         b/b (file)
#
#  c/c (file)         c   (symlink)
#  d   (symlink)      d/d (file)
#
#  e/e (file)         e   (submodule)
#  f   (submodule)    f/f (file)
#
#  g   (submodule)    g   (symlink)
#  h   (symlink)      h   (submodule)
#
# Additionally, the following paths are present on both branches, but with
# different contents:
#
#  i   (file)         i   (file)
#  j   (symlink)      j   (symlink)
#  k   (submodule)    k   (submodule)
#
# And the following paths are only present in one of the branches:
#
#  l/l (file)         -
#  -                  m/m (file)
#
test_expect_success 'setup repo for checkout with various types of changes' '
	test_config_global protocol.file.allow always &&

	git init sub &&
	(
		cd sub &&
		git checkout -b B2 &&
		echo B2 >file &&
		git add file &&
		git commit -m file &&

		git checkout -b B1 &&
		echo B1 >file &&
		git add file &&
		git commit -m file
	) &&

	git init various &&
	(
		cd various &&

		git checkout -b B1 &&
		mkdir a c e &&
		echo a/a >a/a &&
		echo b >b &&
		echo c/c >c/c &&
		test_ln_s_add c d &&
		echo e/e >e/e &&
		git submodule add ../sub f &&
		git submodule add ../sub g &&
		test_ln_s_add c h &&

		echo "B1 i" >i &&
		test_ln_s_add c j &&
		git submodule add -b B1 ../sub k &&
		mkdir l &&
		echo l/l >l/l &&

		git add . &&
		git commit -m B1 &&

		git checkout -b B2 &&
		git rm -rf :^.gitmodules :^k &&
		mkdir b d f &&
		echo a >a &&
		echo b/b >b/b &&
		test_ln_s_add b c &&
		echo d/d >d/d &&
		git submodule add ../sub e &&
		echo f/f >f/f &&
		test_ln_s_add b g &&
		git submodule add ../sub h &&

		echo "B2 i" >i &&
		test_ln_s_add b j &&
		git -C k checkout B2 &&
		mkdir m &&
		echo m/m >m/m &&

		git add . &&
		git commit -m B2 &&

		git checkout --recurse-submodules B1
	)
'

for mode in sequential parallel sequential-fallback
do
	case $mode in
	sequential)          workers=1 threshold=0 expected_workers=0 ;;
	parallel)            workers=2 threshold=0 expected_workers=2 ;;
	sequential-fallback) workers=2 threshold=100 expected_workers=0 ;;
	esac

	test_expect_success "$mode checkout" '
		repo=various_$mode &&
		cp -R -P various $repo &&

		# The just copied files have more recent timestamps than their
		# associated index entries. So refresh the cached timestamps
		# to avoid an "entry not up-to-date" error from `git checkout`.
		# We only have to do this for the submodules as `git checkout`
		# will already refresh the superproject index before performing
		# the up-to-date check.
		#
		git -C $repo submodule foreach "git update-index --refresh" &&

		set_checkout_config $workers $threshold &&
		test_checkout_workers $expected_workers \
			git -C $repo checkout --recurse-submodules B2 &&
		verify_checkout $repo
	'
done

for mode in parallel sequential-fallback
do
	case $mode in
	parallel)            workers=2 threshold=0 expected_workers=2 ;;
	sequential-fallback) workers=2 threshold=100 expected_workers=0 ;;
	esac

	test_expect_success "$mode checkout on clone" '
		test_config_global protocol.file.allow always &&
		repo=various_${mode}_clone &&
		set_checkout_config $workers $threshold &&
		test_checkout_workers $expected_workers \
			git clone --recurse-submodules --branch B2 various $repo &&
		verify_checkout $repo
	'
done

# Just to be paranoid, actually compare the working trees' contents directly.
test_expect_success 'compare the working trees' '
	rm -rf various_*/.git &&
	rm -rf various_*/*/.git &&

	# We use `git diff` instead of `diff -r` because the latter would
	# follow symlinks, and not all `diff` implementations support the
	# `--no-dereference` option.
	#
	git diff --no-index various_sequential various_parallel &&
	git diff --no-index various_sequential various_parallel_clone &&
	git diff --no-index various_sequential various_sequential-fallback &&
	git diff --no-index various_sequential various_sequential-fallback_clone
'

# Currently, each submodule is checked out in a separated child process, but
# these subprocesses must also be able to use parallel checkout workers to
# write the submodules' entries.
test_expect_success 'submodules can use parallel checkout' '
	set_checkout_config 2 0 &&
	git init super &&
	(
		cd super &&
		git init sub &&
		test_commit -C sub A &&
		test_commit -C sub B &&
		git submodule add ./sub &&
		git commit -m sub &&
		rm sub/* &&
		test_checkout_workers 2 git checkout --recurse-submodules .
	)
'

test_expect_success 'parallel checkout respects --[no]-force' '
	set_checkout_config 2 0 &&
	git init dirty &&
	(
		cd dirty &&
		mkdir D &&
		test_commit D/F &&
		test_commit F &&

		rm -rf D &&
		echo changed >D &&
		echo changed >F.t &&

		# We expect 0 workers because there is nothing to be done
		test_checkout_workers 0 git checkout HEAD &&
		test_path_is_file D &&
		grep changed D &&
		grep changed F.t &&

		test_checkout_workers 2 git checkout --force HEAD &&
		test_path_is_dir D &&
		grep D/F D/F.t &&
		grep F F.t
	)
'

test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading dirs' '
	set_checkout_config 2 0 &&
	git init symlinks &&
	(
		cd symlinks &&
		mkdir D untracked &&
		# Commit 2 files to have enough work for 2 parallel workers
		test_commit D/A &&
		test_commit D/B &&
		rm -rf D &&
		ln -s untracked D &&

		test_checkout_workers 2 git checkout --force HEAD &&
		! test -h D &&
		grep D/A D/A.t &&
		grep D/B D/B.t
	)
'

# This test is here (and not in e.g. t2022-checkout-paths.sh), because we
# check the final report including sequential, parallel, and delayed entries
# all at the same time. So we must have finer control of the parallel checkout
# variables.
test_expect_success '"git checkout ." report should not include failed entries' '
	test_config_global filter.delay.process \
		"test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" &&
	test_config_global filter.delay.required true &&
	test_config_global filter.cat.clean cat  &&
	test_config_global filter.cat.smudge cat  &&
	test_config_global filter.cat.required true  &&

	set_checkout_config 2 0 &&
	git init failed_entries &&
	(
		cd failed_entries &&
		cat >.gitattributes <<-EOF &&
		*delay*              filter=delay
		parallel-ineligible* filter=cat
		EOF
		echo a >missing-delay.a &&
		echo a >parallel-ineligible.a &&
		echo a >parallel-eligible.a &&
		echo b >success-delay.b &&
		echo b >parallel-ineligible.b &&
		echo b >parallel-eligible.b &&
		git add -A &&
		git commit -m files &&

		a_blob="$(git rev-parse :parallel-ineligible.a)" &&
		rm .git/objects/$(test_oid_to_path $a_blob) &&
		rm *.a *.b &&

		test_checkout_workers 2 test_must_fail git checkout . 2>err &&

		# All *.b entries should succeed and all *.a entries should fail:
		#  - missing-delay.a: the delay filter will drop this path
		#  - parallel-*.a: the blob will be missing
		#
		grep "Updated 3 paths from the index" err &&
		test_stdout_line_count = 3 ls *.b &&
		! ls *.a
	)
'

# Regression test: parallel checkout + fscache stale directory listing.
#
# When checkout.workers > 1, checkout_entry_ca() enqueues files for deferred
# writing instead of writing them inline. The inline write_entry() path calls
# flush_fscache() after each file, keeping the Windows fscache in sync with
# newly-created directories. The deferred path skips this flush, so
# has_dirs_only_path() sees stale ENOENT for directories that mkdir() just
# created. The recovery path in create_directories() then tries to unlink+
# recreate the directory, which fails because it already has children.
#
# The trigger is: two files sharing a parent directory that does not yet exist
# on disk when `git checkout <tree> -- <pathspec>` runs.
test_expect_success MINGW 'parallel checkout with fscache does not fail on new directories' '
	git init fscache-pc &&
	(
		cd fscache-pc &&
		git config core.fscache true &&

		# Commit B1: files in a nested directory
		mkdir -p sub/deep/dir &&
		echo one >sub/deep/dir/file1.txt &&
		echo two >sub/deep/dir/file2.txt &&
		git add sub &&
		git commit -m "B1: with sub/deep/dir" &&
		git tag B1 &&

		# Commit B2: the directory is gone
		git rm -rf sub &&
		git commit -m "B2: without sub" &&

		# Now restore both files from B1 with parallel checkout.
		# This is the pathspec checkout path (checkout_worktree in
		# builtin/checkout.c), which defers writes via enqueue_checkout
		# when workers > 1 and does not flush fscache between entries.
		git -c checkout.workers=2 \
		    -c checkout.thresholdForParallelism=0 \
		    checkout B1 -- sub/deep/dir/file1.txt sub/deep/dir/file2.txt &&

		# Verify both files are correctly restored
		echo one >expect1 &&
		echo two >expect2 &&
		test_cmp expect1 sub/deep/dir/file1.txt &&
		test_cmp expect2 sub/deep/dir/file2.txt
	)
'

test_done
