#!/bin/sh

test_description='undoing resolution'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

check_resolve_undo () {
	msg=$1
	shift
	while case $# in
	0)	break ;;
	1|2|3)	die "Bug in check-resolve-undo test" ;;
	esac
	do
		path=$1
		shift
		for stage in 1 2 3
		do
			sha1=$1
			shift
			case "$sha1" in
			'') continue ;;
			esac
			sha1=$(git rev-parse --verify "$sha1")
			printf "100644 %s %s\t%s\n" $sha1 $stage $path
		done
	done >"$msg.expect" &&
	git ls-files --resolve-undo >"$msg.actual" &&
	test_cmp "$msg.expect" "$msg.actual"
}

prime_resolve_undo () {
	git reset --hard &&
	git checkout second^0 &&
	test_tick &&
	test_must_fail git merge third^0 &&
	check_resolve_undo empty &&

	# how should the conflict be resolved?
	case "$1" in
	remove)
		rm -f file/le && git rm fi/le
		;;
	*) # modify
		echo different >fi/le && git add fi/le
		;;
	esac
	check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le
}

test_expect_success setup '
	mkdir fi &&
	printf "a\0a" >binary &&
	git add binary &&
	test_commit initial fi/le first &&
	git branch side &&
	git branch another &&
	printf "a\0b" >binary &&
	git add binary &&
	test_commit second fi/le second &&
	git checkout side &&
	test_commit third fi/le third &&
	git branch add-add &&
	git checkout another &&
	test_commit fourth fi/le fourth &&
	git checkout add-add &&
	test_commit fifth add-differently &&
	git checkout main
'

test_expect_success 'add records switch clears' '
	prime_resolve_undo &&
	test_tick &&
	git commit -m merged &&
	echo committing keeps &&
	check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
	git checkout second^0 &&
	echo switching clears &&
	check_resolve_undo cleared
'

test_expect_success 'rm records reset clears' '
	prime_resolve_undo &&
	test_tick &&
	git commit -m merged &&
	echo committing keeps &&
	check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&

	echo merge clears upfront &&
	test_must_fail git merge fourth^0 &&
	check_resolve_undo nuked &&

	git rm -f fi/le &&
	echo resolving records &&
	check_resolve_undo recorded fi/le initial:fi/le HEAD:fi/le fourth:fi/le &&

	git reset --hard &&
	echo resetting discards &&
	check_resolve_undo discarded
'

test_expect_success 'plumbing clears' '
	prime_resolve_undo &&
	test_tick &&
	git commit -m merged &&
	echo committing keeps &&
	check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&

	echo plumbing clear &&
	git update-index --clear-resolve-undo &&
	check_resolve_undo cleared
'

test_expect_success 'add records checkout -m undoes' '
	prime_resolve_undo &&
	git diff HEAD &&
	git checkout --conflict=merge fi/le &&
	echo checkout used the record and removed it &&
	check_resolve_undo removed &&
	echo the index and the work tree is unmerged again &&
	git diff >actual &&
	grep "^++<<<<<<<" actual
'

test_expect_success 'unmerge with plumbing' '
	prime_resolve_undo &&
	git update-index --unresolve fi/le &&
	git ls-files --resolve-undo fi/le >actual &&
	test_must_be_empty actual &&
	git ls-files -u >actual &&
	test_line_count = 3 actual
'

test_expect_success 'unmerge can be done even after committing' '
	prime_resolve_undo &&
	git commit -m "record to nuke MERGE_HEAD" &&
	git update-index --unresolve fi/le &&
	git ls-files --resolve-undo fi/le >actual &&
	test_must_be_empty actual &&
	git ls-files -u >actual &&
	test_line_count = 3 actual
'

test_expect_success 'unmerge removal' '
	prime_resolve_undo remove &&
	git update-index --unresolve fi/le &&
	git ls-files --resolve-undo fi/le >actual &&
	test_must_be_empty actual &&
	git ls-files -u >actual &&
	test_line_count = 3 actual
'

test_expect_success 'unmerge removal after committing' '
	prime_resolve_undo remove &&
	git commit -m "record to nuke MERGE_HEAD" &&
	git update-index --unresolve fi/le &&
	git ls-files --resolve-undo fi/le >actual &&
	test_must_be_empty actual &&
	git ls-files -u >actual &&
	test_line_count = 3 actual
'

test_expect_success 'rerere and rerere forget' '
	mkdir .git/rr-cache &&
	prime_resolve_undo &&
	echo record the resolution &&
	git rerere &&
	rerere_id=$(cd .git/rr-cache && echo */postimage) &&
	rerere_id=${rerere_id%/postimage} &&
	test -f .git/rr-cache/$rerere_id/postimage &&
	git checkout -m fi/le &&
	echo resurrect the conflict &&
	grep "^=======" fi/le &&
	echo reresolve the conflict &&
	git rerere &&
	test "z$(cat fi/le)" = zdifferent &&
	echo register the resolution again &&
	git add fi/le &&
	check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
	test -z "$(git ls-files -u)" &&
	git rerere forget fi/le &&
	! test -f .git/rr-cache/$rerere_id/postimage &&
	tr "\0" "\n" <.git/MERGE_RR >actual &&
	echo "$rerere_id	fi/le" >expect &&
	test_cmp expect actual
'

test_expect_success 'rerere and rerere forget (subdirectory)' '
	rm -fr .git/rr-cache &&
	mkdir .git/rr-cache &&
	prime_resolve_undo &&
	echo record the resolution &&
	(cd fi && git rerere) &&
	rerere_id=$(cd .git/rr-cache && echo */postimage) &&
	rerere_id=${rerere_id%/postimage} &&
	test -f .git/rr-cache/$rerere_id/postimage &&
	(cd fi && git checkout -m le) &&
	echo resurrect the conflict &&
	grep "^=======" fi/le &&
	echo reresolve the conflict &&
	(cd fi && git rerere) &&
	test "z$(cat fi/le)" = zdifferent &&
	echo register the resolution again &&
	(cd fi && git add le) &&
	check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
	test -z "$(git ls-files -u)" &&
	(cd fi && git rerere forget le) &&
	! test -f .git/rr-cache/$rerere_id/postimage &&
	tr "\0" "\n" <.git/MERGE_RR >actual &&
	echo "$rerere_id	fi/le" >expect &&
	test_cmp expect actual
'

test_expect_success 'rerere forget (binary)' '
	git checkout -f side &&
	test_commit --printf binary binary "a\0c" &&
	test_must_fail git merge second &&
	git rerere forget binary
'

test_expect_success 'rerere forget (add-add conflict)' '
	git checkout -f main &&
	echo main >add-differently &&
	git add add-differently &&
	git commit -m "add differently" &&
	test_must_fail git merge fifth &&
	git rerere forget add-differently 2>actual &&
	test_grep "no remembered" actual
'

test_expect_success 'resolve-undo keeps blobs from gc' '
	git checkout -f main &&

	# First make sure we do not have any cruft left in the object store
	git repack -a -d &&
	git prune --expire=now &&
	git prune-packed &&
	git gc --prune=now &&
	git fsck --unreachable >cruft &&
	test_must_be_empty cruft &&

	# Now add three otherwise unreferenced blob objects to the index
	git reset --hard &&
	B1=$(echo "resolve undo test data 1" | git hash-object -w --stdin) &&
	B2=$(echo "resolve undo test data 2" | git hash-object -w --stdin) &&
	B3=$(echo "resolve undo test data 3" | git hash-object -w --stdin) &&
	git update-index --add --index-info <<-EOF &&
	100644 $B1 1	frotz
	100644 $B2 2	frotz
	100644 $B3 3	frotz
	EOF

	# These three blob objects are reachable (only) from the index
	git fsck --unreachable >cruft &&
	test_must_be_empty cruft &&
	# and they should be protected from GC
	git gc --prune=now &&
	git cat-file -e $B1 &&
	git cat-file -e $B2 &&
	git cat-file -e $B3 &&

	# Now resolve the conflicted path
	B0=$(echo "resolve undo test data 0" | git hash-object -w --stdin) &&
	git update-index --add --cacheinfo 100644,$B0,frotz &&

	# These three blob objects are now reachable only from the resolve-undo
	git fsck --unreachable >cruft &&
	test_must_be_empty cruft &&

	# and they should survive GC
	git gc --prune=now &&
	git cat-file -e $B0 &&
	git cat-file -e $B1 &&
	git cat-file -e $B2 &&
	git cat-file -e $B3 &&

	# Now we switch away, which nukes resolve-undo, and
	# blobs B0..B3 would become dangling.  fsck should
	# notice that they are now unreachable.
	git checkout -f side &&
	git fsck --unreachable >cruft &&
	sort cruft >actual &&
	sort <<-EOF >expect &&
	unreachable blob $B0
	unreachable blob $B1
	unreachable blob $B2
	unreachable blob $B3
	EOF
	test_cmp expect actual &&

	# And they should go away when gc runs.
	git gc --prune=now &&
	git fsck --unreachable >cruft &&
	test_must_be_empty cruft &&

	test_must_fail git cat-file -e $B0 &&
	test_must_fail git cat-file -e $B1 &&
	test_must_fail git cat-file -e $B2 &&
	test_must_fail git cat-file -e $B3
'

test_done
