#!/bin/sh

test_description='split index mode tests'

. ./test-lib.sh

# We need total control of index splitting here
sane_unset GIT_TEST_SPLIT_INDEX
# A couple of tests expect the index to have a specific checksum,
# but the presence of the optional FSMN extension would interfere
# with those checks, so disable it in this test script.
sane_unset GIT_FSMONITOR_TEST

# Create a file named as $1 with content read from stdin.
# Set the file's mtime to a few seconds in the past to avoid racy situations.
create_non_racy_file () {
	cat >"$1" &&
	test-tool chmtime =-5 "$1"
}

test_expect_success 'enable split index' '
	git config splitIndex.maxPercentChange 100 &&
	git update-index --split-index &&
	test-tool dump-split-index .git/index >actual &&
	indexversion=$(test-tool index-version <.git/index) &&
	if test "$indexversion" = "4"
	then
		own=432ef4b63f32193984f339431fd50ca796493569
		base=508851a7f0dfa8691e9f69c7f055865389012491
	else
		own=8299b0bcd1ac364e5f1d7768efb62fa2da79a339
		base=39d890139ee5356c7ef572216cebcd27aa41f9df
	fi &&
	cat >expect <<-EOF &&
	own $own
	base $base
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'add one file' '
	create_non_racy_file one &&
	git update-index --add one &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	base $base
	100644 $EMPTY_BLOB 0	one
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'disable split index' '
	git update-index --no-split-index &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	BASE=$(test-tool dump-split-index .git/index | sed -n "s/^own/base/p") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	not a split index
	EOF
	test_cmp expect actual
'

test_expect_success 'enable split index again, "one" now belongs to base index"' '
	git update-index --split-index &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'modify original file, base index untouched' '
	echo modified | create_non_racy_file one &&
	git update-index one &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	q_to_tab >expect <<-EOF &&
	$BASE
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q
	replacements: 0
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'add another file, which stays index' '
	create_non_racy_file two &&
	git update-index --add two &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0	one
	100644 $EMPTY_BLOB 0	two
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	q_to_tab >expect <<-EOF &&
	$BASE
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q
	100644 $EMPTY_BLOB 0	two
	replacements: 0
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'remove file not in base index' '
	git update-index --force-remove two &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	q_to_tab >expect <<-EOF &&
	$BASE
	100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q
	replacements: 0
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'remove file in base index' '
	git update-index --force-remove one &&
	git ls-files --stage >ls-files.actual &&
	test_must_be_empty ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions: 0
	EOF
	test_cmp expect actual
'

test_expect_success 'add original file back' '
	create_non_racy_file one &&
	git update-index --add one &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	100644 $EMPTY_BLOB 0	one
	replacements:
	deletions: 0
	EOF
	test_cmp expect actual
'

test_expect_success 'add new file' '
	create_non_racy_file two &&
	git update-index --add two &&
	git ls-files --stage >actual &&
	cat >expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	100644 $EMPTY_BLOB 0	two
	EOF
	test_cmp expect actual
'

test_expect_success 'unify index, two files remain' '
	git update-index --no-split-index &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 $EMPTY_BLOB 0	one
	100644 $EMPTY_BLOB 0	two
	EOF
	test_cmp ls-files.expect ls-files.actual &&

	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	not a split index
	EOF
	test_cmp expect actual
'

test_expect_success 'rev-parse --shared-index-path' '
	test_create_repo split-index &&
	(
		cd split-index &&
		git update-index --split-index &&
		echo .git/sharedindex* >expect &&
		git rev-parse --shared-index-path >actual &&
		test_cmp expect actual &&
		mkdir subdirectory &&
		cd subdirectory &&
		echo ../.git/sharedindex* >expect &&
		git rev-parse --shared-index-path >actual &&
		test_cmp expect actual
	)
'

test_expect_success 'set core.splitIndex config variable to true' '
	git config core.splitIndex true &&
	create_non_racy_file three &&
	git update-index --add three &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	one
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	three
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	two
	EOF
	test_cmp ls-files.expect ls-files.actual &&
	BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'set core.splitIndex config variable to false' '
	git config core.splitIndex false &&
	git update-index --force-remove three &&
	git ls-files --stage >ls-files.actual &&
	cat >ls-files.expect <<-EOF &&
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	one
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	two
	EOF
	test_cmp ls-files.expect ls-files.actual &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	not a split index
	EOF
	test_cmp expect actual
'

test_expect_success 'set core.splitIndex config variable back to true' '
	git config core.splitIndex true &&
	create_non_racy_file three &&
	git update-index --add three &&
	BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual &&
	create_non_racy_file four &&
	git update-index --add four &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	four
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'check behavior with splitIndex.maxPercentChange unset' '
	git config --unset splitIndex.maxPercentChange &&
	create_non_racy_file five &&
	git update-index --add five &&
	BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual &&
	create_non_racy_file six &&
	git update-index --add six &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	six
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'check splitIndex.maxPercentChange set to 0' '
	git config splitIndex.maxPercentChange 0 &&
	create_non_racy_file seven &&
	git update-index --add seven &&
	BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual &&
	create_non_racy_file eight &&
	git update-index --add eight &&
	BASE=$(test-tool dump-split-index .git/index | grep "^base") &&
	test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
	cat >expect <<-EOF &&
	$BASE
	replacements:
	deletions:
	EOF
	test_cmp expect actual
'

test_expect_success 'shared index files expire after 2 weeks by default' '
	create_non_racy_file ten &&
	git update-index --add ten &&
	test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
	just_under_2_weeks_ago=$((5-14*86400)) &&
	test-tool chmtime =$just_under_2_weeks_ago .git/sharedindex.* &&
	create_non_racy_file eleven &&
	git update-index --add eleven &&
	test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
	just_over_2_weeks_ago=$((-1-14*86400)) &&
	test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* &&
	create_non_racy_file twelve &&
	git update-index --add twelve &&
	test $(ls .git/sharedindex.* | wc -l) -le 2
'

test_expect_success 'check splitIndex.sharedIndexExpire set to 16 days' '
	git config splitIndex.sharedIndexExpire "16.days.ago" &&
	test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* &&
	create_non_racy_file thirteen &&
	git update-index --add thirteen &&
	test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
	just_over_16_days_ago=$((-1-16*86400)) &&
	test-tool chmtime =$just_over_16_days_ago .git/sharedindex.* &&
	create_non_racy_file fourteen &&
	git update-index --add fourteen &&
	test $(ls .git/sharedindex.* | wc -l) -le 2
'

test_expect_success 'check splitIndex.sharedIndexExpire set to "never" and "now"' '
	git config splitIndex.sharedIndexExpire never &&
	just_10_years_ago=$((-365*10*86400)) &&
	test-tool chmtime =$just_10_years_ago .git/sharedindex.* &&
	create_non_racy_file fifteen &&
	git update-index --add fifteen &&
	test $(ls .git/sharedindex.* | wc -l) -gt 2 &&
	git config splitIndex.sharedIndexExpire now &&
	just_1_second_ago=-1 &&
	test-tool chmtime =$just_1_second_ago .git/sharedindex.* &&
	create_non_racy_file sixteen &&
	git update-index --add sixteen &&
	test $(ls .git/sharedindex.* | wc -l) -le 2
'

while read -r mode modebits
do
	test_expect_success POSIXPERM "split index respects core.sharedrepository $mode" '
		# Remove existing shared index files
		git config core.splitIndex false &&
		git update-index --force-remove one &&
		rm -f .git/sharedindex.* &&
		# Create one new shared index file
		git config core.sharedrepository "$mode" &&
		git config core.splitIndex true &&
		create_non_racy_file one &&
		git update-index --add one &&
		echo "$modebits" >expect &&
		test_modebits .git/index >actual &&
		test_cmp expect actual &&
		shared=$(ls .git/sharedindex.*) &&
		case "$shared" in
		*" "*)
			# we have more than one???
			false ;;
		*)
			test_modebits "$shared" >actual &&
			test_cmp expect actual ;;
		esac
	'
done <<\EOF
0666 -rw-rw-rw-
0642 -rw-r---w-
EOF

test_expect_success POSIXPERM,SANITY 'graceful handling when splitting index is not allowed' '
	test_create_repo ro &&
	(
		cd ro &&
		test_commit initial &&
		git update-index --split-index &&
		test -f .git/sharedindex.*
	) &&
	cp ro/.git/index new-index &&
	test_when_finished "chmod u+w ro/.git" &&
	chmod u-w ro/.git &&
	GIT_INDEX_FILE="$(pwd)/new-index" git -C ro update-index --split-index &&
	chmod u+w ro/.git &&
	rm ro/.git/sharedindex.* &&
	GIT_INDEX_FILE=new-index git ls-files >actual &&
	echo initial.t >expected &&
	test_cmp expected actual
'

test_expect_success 'writing split index with null sha1 does not write cache tree' '
	git config core.splitIndex true &&
	git config splitIndex.maxPercentChange 0 &&
	git commit -m "commit" &&
	{
		git ls-tree HEAD &&
		printf "160000 commit $ZERO_OID\\tbroken\\n"
	} >broken-tree &&
	echo "add broken entry" >msg &&

	tree=$(git mktree <broken-tree) &&
	test_tick &&
	commit=$(git commit-tree $tree -p HEAD <msg) &&
	git update-ref HEAD "$commit" &&
	GIT_ALLOW_NULL_SHA1=1 git reset --hard &&
	test_might_fail test-tool dump-cache-tree >cache-tree.out &&
	test_line_count = 0 cache-tree.out
'

test_done
