git/t/t6302-for-each-ref-filter.sh

#!/bin/sh

test_description='test for-each-refs usage of ref-filter APIs'

TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh

test_expect_success 'setup some history and refs' '
	test_commit one &&
	git branch -M main &&
	test_commit two &&
	test_commit three &&
	git checkout -b side &&
	test_commit four &&
	git tag -m "An annotated tag" annotated-tag &&
	git tag -m "Annonated doubly" doubly-annotated-tag annotated-tag &&

	# Note that these "signed" tags might not actually be signed.
	# Tests which care about the distinction should be marked
	# with the GPG prereq.
	if test_have_prereq GPG
	then
		sign=-s
	else
		sign=
	fi &&
	git tag $sign -m "A signed tag" signed-tag &&
	git tag $sign -m "Signed doubly" doubly-signed-tag signed-tag &&

	git checkout main &&
	git update-ref refs/odd/spot main
'

test_expect_success '--include-root-refs pattern prints pseudorefs' '
	cat >expect <<-\EOF &&
	HEAD
	ORIG_HEAD
	refs/heads/main
	refs/heads/side
	refs/odd/spot
	refs/tags/annotated-tag
	refs/tags/doubly-annotated-tag
	refs/tags/doubly-signed-tag
	refs/tags/four
	refs/tags/one
	refs/tags/signed-tag
	refs/tags/three
	refs/tags/two
	EOF
	git update-ref ORIG_HEAD main &&
	git for-each-ref --format="%(refname)" --include-root-refs >actual &&
	test_cmp expect actual
'

test_expect_success '--include-root-refs pattern does not print special refs' '
	test_when_finished "rm -rf repo" &&
	git init repo &&
	(
		cd repo &&
		test_commit initial &&
		git rev-parse HEAD >.git/MERGE_HEAD &&
		git for-each-ref --format="%(refname)" --include-root-refs >actual &&
		cat >expect <<-EOF &&
		HEAD
		$(git symbolic-ref HEAD)
		refs/tags/initial
		EOF
		test_cmp expect actual
	)
'

test_expect_success '--include-root-refs with other patterns' '
	cat >expect <<-\EOF &&
	HEAD
	ORIG_HEAD
	EOF
	git update-ref ORIG_HEAD main &&
	git for-each-ref --format="%(refname)" --include-root-refs "*HEAD" >actual &&
	test_cmp expect actual
'

test_expect_success '--include-root-refs omits dangling symrefs' '
	test_when_finished "rm -rf repo" &&
	git init repo &&
	(
		cd repo &&
		test_commit initial &&
		git symbolic-ref DANGLING_HEAD refs/heads/missing &&
		cat >expect <<-EOF &&
		HEAD
		$(git symbolic-ref HEAD)
		refs/tags/initial
		EOF
		git for-each-ref --format="%(refname)" --include-root-refs >actual &&
		test_cmp expect actual
	)
'

test_expect_success 'filtering with --points-at' '
	cat >expect <<-\EOF &&
	refs/heads/main
	refs/odd/spot
	refs/tags/three
	EOF
	git for-each-ref --format="%(refname)" --points-at=main >actual &&
	test_cmp expect actual
'

test_expect_success 'check signed tags with --points-at' '
	sed -e "s/Z$//" >expect <<-\EOF &&
	refs/heads/side Z
	refs/tags/annotated-tag four
	refs/tags/doubly-annotated-tag four
	refs/tags/doubly-signed-tag four
	refs/tags/four Z
	refs/tags/signed-tag four
	EOF
	git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
	test_cmp expect actual
'

test_expect_success 'filtering with --merged' '
	cat >expect <<-\EOF &&
	refs/heads/main
	refs/odd/spot
	refs/tags/one
	refs/tags/three
	refs/tags/two
	EOF
	git for-each-ref --format="%(refname)" --merged=main >actual &&
	test_cmp expect actual
'

test_expect_success 'filtering with --no-merged' '
	cat >expect <<-\EOF &&
	refs/heads/side
	refs/tags/annotated-tag
	refs/tags/doubly-annotated-tag
	refs/tags/doubly-signed-tag
	refs/tags/four
	refs/tags/signed-tag
	EOF
	git for-each-ref --format="%(refname)" --no-merged=main >actual &&
	test_cmp expect actual
'

test_expect_success 'filtering with --contains' '
	cat >expect <<-\EOF &&
	refs/heads/main
	refs/heads/side
	refs/odd/spot
	refs/tags/annotated-tag
	refs/tags/doubly-annotated-tag
	refs/tags/doubly-signed-tag
	refs/tags/four
	refs/tags/signed-tag
	refs/tags/three
	refs/tags/two
	EOF
	git for-each-ref --format="%(refname)" --contains=two >actual &&
	test_cmp expect actual
'

test_expect_success 'filtering with --no-contains' '
	cat >expect <<-\EOF &&
	refs/tags/one
	EOF
	git for-each-ref --format="%(refname)" --no-contains=two >actual &&
	test_cmp expect actual
'

test_expect_success 'filtering with --contains and --no-contains' '
	cat >expect <<-\EOF &&
	refs/tags/two
	EOF
	git for-each-ref --format="%(refname)" --contains=two --no-contains=three >actual &&
	test_cmp expect actual
'

test_expect_success '%(color) must fail' '
	test_must_fail git for-each-ref --format="%(color)%(refname)"
'

test_expect_success '%(color:#aa22ac) must succeed' '
	test_when_finished rm -rf test &&
	git init test &&
	(
		cd test &&
		test_commit initial &&
		git branch -M main &&
		cat >expect <<-\EOF &&
		refs/heads/main
		refs/tags/initial
		EOF
		git remote add origin nowhere &&
		git config branch.main.remote origin &&
		git config branch.main.merge refs/heads/main &&
		git for-each-ref --format="%(color:#aa22ac)%(refname)" >actual &&
		test_cmp expect actual
	)
'

test_expect_success 'left alignment is default' '
	cat >expect <<-\EOF &&
	refname is refs/heads/main    |refs/heads/main
	refname is refs/heads/side    |refs/heads/side
	refname is refs/odd/spot      |refs/odd/spot
	refname is refs/tags/annotated-tag|refs/tags/annotated-tag
	refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag
	refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag
	refname is refs/tags/four     |refs/tags/four
	refname is refs/tags/one      |refs/tags/one
	refname is refs/tags/signed-tag|refs/tags/signed-tag
	refname is refs/tags/three    |refs/tags/three
	refname is refs/tags/two      |refs/tags/two
	EOF
	git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual &&
	test_cmp expect actual
'

test_expect_success 'middle alignment' '
	cat >expect <<-\EOF &&
	|  refname is refs/heads/main  |refs/heads/main
	|  refname is refs/heads/side  |refs/heads/side
	|   refname is refs/odd/spot   |refs/odd/spot
	|refname is refs/tags/annotated-tag|refs/tags/annotated-tag
	|refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag
	|refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag
	|  refname is refs/tags/four   |refs/tags/four
	|   refname is refs/tags/one   |refs/tags/one
	|refname is refs/tags/signed-tag|refs/tags/signed-tag
	|  refname is refs/tags/three  |refs/tags/three
	|   refname is refs/tags/two   |refs/tags/two
	EOF
	git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual &&
	test_cmp expect actual
'

test_expect_success 'right alignment' '
	cat >expect <<-\EOF &&
	|    refname is refs/heads/main|refs/heads/main
	|    refname is refs/heads/side|refs/heads/side
	|      refname is refs/odd/spot|refs/odd/spot
	|refname is refs/tags/annotated-tag|refs/tags/annotated-tag
	|refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag
	|refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag
	|     refname is refs/tags/four|refs/tags/four
	|      refname is refs/tags/one|refs/tags/one
	|refname is refs/tags/signed-tag|refs/tags/signed-tag
	|    refname is refs/tags/three|refs/tags/three
	|      refname is refs/tags/two|refs/tags/two
	EOF
	git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual &&
	test_cmp expect actual
'

cat >expect <<-\EOF
|        refname is refs/heads/main        |refs/heads/main
|        refname is refs/heads/side        |refs/heads/side
|         refname is refs/odd/spot         |refs/odd/spot
|    refname is refs/tags/annotated-tag    |refs/tags/annotated-tag
|refname is refs/tags/doubly-annotated-tag |refs/tags/doubly-annotated-tag
|  refname is refs/tags/doubly-signed-tag  |refs/tags/doubly-signed-tag
|        refname is refs/tags/four         |refs/tags/four
|         refname is refs/tags/one         |refs/tags/one
|     refname is refs/tags/signed-tag      |refs/tags/signed-tag
|        refname is refs/tags/three        |refs/tags/three
|         refname is refs/tags/two         |refs/tags/two
EOF

test_align_permutations() {
	while read -r option
	do
		test_expect_success "align:$option" '
			git for-each-ref --format="|%(align:$option)refname is %(refname)%(end)|%(refname)" >actual &&
			test_cmp expect actual
		'
	done
}

test_align_permutations <<-\EOF
	middle,42
	42,middle
	position=middle,42
	42,position=middle
	middle,width=42
	width=42,middle
	position=middle,width=42
	width=42,position=middle
EOF

# Last one wins (silently) when multiple arguments of the same type are given

test_align_permutations <<-\EOF
	32,width=42,middle
	width=30,42,middle
	width=42,position=right,middle
	42,right,position=middle
EOF

# Individual atoms inside %(align:...) and %(end) must not be quoted.

test_expect_success 'alignment with format quote' "
	cat >expect <<-\EOF &&
	|'       '\''main| A U Thor'\''       '|
	|'       '\''side| A U Thor'\''       '|
	|'     '\''odd/spot| A U Thor'\''     '|
	|'      '\''annotated-tag| '\''       '|
	|'   '\''doubly-annotated-tag| '\''   '|
	|'    '\''doubly-signed-tag| '\''     '|
	|'       '\''four| A U Thor'\''       '|
	|'       '\''one| A U Thor'\''        '|
	|'        '\''signed-tag| '\''        '|
	|'      '\''three| A U Thor'\''       '|
	|'       '\''two| A U Thor'\''        '|
	EOF
	git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual &&
	test_cmp expect actual
"

test_expect_success 'nested alignment with quote formatting' "
	cat >expect <<-\EOF &&
	|'           main               '|
	|'           side               '|
	|'       odd/spot               '|
	|'  annotated-tag               '|
	|'doubly-annotated-tag          '|
	|'doubly-signed-tag             '|
	|'           four               '|
	|'            one               '|
	|'     signed-tag               '|
	|'          three               '|
	|'            two               '|
	EOF
	git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&
	test_cmp expect actual
"

test_expect_success 'check `%(contents:lines=1)`' '
	cat >expect <<-\EOF &&
	main |three
	side |four
	odd/spot |three
	annotated-tag |An annotated tag
	doubly-annotated-tag |Annonated doubly
	doubly-signed-tag |Signed doubly
	four |four
	one |one
	signed-tag |A signed tag
	three |three
	two |two
	EOF
	git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual &&
	test_cmp expect actual
'

test_expect_success 'check `%(contents:lines=0)`' '
	cat >expect <<-\EOF &&
	main |
	side |
	odd/spot |
	annotated-tag |
	doubly-annotated-tag |
	doubly-signed-tag |
	four |
	one |
	signed-tag |
	three |
	two |
	EOF
	git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual &&
	test_cmp expect actual
'

test_expect_success 'check `%(contents:lines=99999)`' '
	cat >expect <<-\EOF &&
	main |three
	side |four
	odd/spot |three
	annotated-tag |An annotated tag
	doubly-annotated-tag |Annonated doubly
	doubly-signed-tag |Signed doubly
	four |four
	one |one
	signed-tag |A signed tag
	three |three
	two |two
	EOF
	git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual &&
	test_cmp expect actual
'

test_expect_success '`%(contents:lines=-1)` should fail' '
	test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)"
'

test_expect_success 'setup for version sort' '
	test_commit foo1.3 &&
	test_commit foo1.6 &&
	test_commit foo1.10
'

test_expect_success 'version sort' '
	git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
	cat >expect <<-\EOF &&
	foo1.3
	foo1.6
	foo1.10
	EOF
	test_cmp expect actual
'

test_expect_success 'version sort (shortened)' '
	git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
	cat >expect <<-\EOF &&
	foo1.3
	foo1.6
	foo1.10
	EOF
	test_cmp expect actual
'

test_expect_success 'reverse version sort' '
	git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
	cat >expect <<-\EOF &&
	foo1.10
	foo1.6
	foo1.3
	EOF
	test_cmp expect actual
'

test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' '
	test_must_fail git for-each-ref --format="%(if)" &&
	test_must_fail git for-each-ref --format="%(then) %(end)" &&
	test_must_fail git for-each-ref --format="%(else) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" &&
	test_must_fail git for-each-ref --format="%(then) %(else) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(then) %(else)" &&
	test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" &&
	test_must_fail git for-each-ref --format="%(if) %(end)"
'

test_expect_success 'check %(if)...%(then)...%(end) atoms' '
	git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual &&
	cat >expect <<-\EOF &&
	refs/heads/main Author: A U Thor
	refs/heads/side Author: A U Thor
	refs/odd/spot Author: A U Thor
	refs/tags/annotated-tag
	refs/tags/doubly-annotated-tag
	refs/tags/doubly-signed-tag
	refs/tags/foo1.10 Author: A U Thor
	refs/tags/foo1.3 Author: A U Thor
	refs/tags/foo1.6 Author: A U Thor
	refs/tags/four Author: A U Thor
	refs/tags/one Author: A U Thor
	refs/tags/signed-tag
	refs/tags/three Author: A U Thor
	refs/tags/two Author: A U Thor
	EOF
	test_cmp expect actual
'

test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' '
	git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual &&
	cat >expect <<-\EOF &&
	A U Thor: refs/heads/main
	A U Thor: refs/heads/side
	A U Thor: refs/odd/spot
	No author: refs/tags/annotated-tag
	No author: refs/tags/doubly-annotated-tag
	No author: refs/tags/doubly-signed-tag
	A U Thor: refs/tags/foo1.10
	A U Thor: refs/tags/foo1.3
	A U Thor: refs/tags/foo1.6
	A U Thor: refs/tags/four
	A U Thor: refs/tags/one
	No author: refs/tags/signed-tag
	A U Thor: refs/tags/three
	A U Thor: refs/tags/two
	EOF
	test_cmp expect actual
'
test_expect_success 'ignore spaces in %(if) atom usage' '
	git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual &&
	cat >expect <<-\EOF &&
	main: Head ref
	side: Not Head ref
	odd/spot: Not Head ref
	annotated-tag: Not Head ref
	doubly-annotated-tag: Not Head ref
	doubly-signed-tag: Not Head ref
	foo1.10: Not Head ref
	foo1.3: Not Head ref
	foo1.6: Not Head ref
	four: Not Head ref
	one: Not Head ref
	signed-tag: Not Head ref
	three: Not Head ref
	two: Not Head ref
	EOF
	test_cmp expect actual
'

test_expect_success 'check %(if:equals=<string>)' '
	git for-each-ref --format="%(if:equals=main)%(refname:short)%(then)Found main%(else)Not main%(end)" refs/heads/ >actual &&
	cat >expect <<-\EOF &&
	Found main
	Not main
	EOF
	test_cmp expect actual
'

test_expect_success 'check %(if:notequals=<string>)' '
	git for-each-ref --format="%(if:notequals=main)%(refname:short)%(then)Not main%(else)Found main%(end)" refs/heads/ >actual &&
	cat >expect <<-\EOF &&
	Found main
	Not main
	EOF
	test_cmp expect actual
'

test_expect_success '--merged is compatible with --no-merged' '
	git for-each-ref --merged HEAD --no-merged HEAD
'

test_expect_success 'validate worktree atom' '
	cat >expect <<-EOF &&
	main: $(pwd)
	main_worktree: $(pwd)/worktree_dir
	side: not checked out
	EOF
	git worktree add -b main_worktree worktree_dir main &&
	git for-each-ref --format="%(refname:short): %(if)%(worktreepath)%(then)%(worktreepath)%(else)not checked out%(end)" refs/heads/ >actual &&
	rm -r worktree_dir &&
	git worktree prune &&
	test_cmp expect actual
'

test_done