#!/bin/sh
# The tree page. The checks against foo+bar cover a file name and a branch
# name that both contain a plus, which cgit percent-encodes in a path as
# well as in a query, since a bare plus in a served path reads back as a
# space through a query string.
test_description='Check content on tree page'
. ./setup.sh
test_expect_success 'generate bar/tree' 'cgit_ok_url "bar/tree" >tmp'
test_expect_success 'find file-1' 'grep ">file-1<" tmp'
test_expect_success 'find file-50' 'grep ">file-50<" tmp'
test_expect_success 'the links column has a name' '
grep "
| " tmp
'
test_expect_success 'generate bar/tree/file-50' 'cgit_ok_url "bar/tree/file-50" >tmp'
test_expect_success 'find line 1' '
grep "1" tmp
'
test_expect_success 'a one line file gets no second line number' '
cgit_ok_url "bar/tree/file-50" >tmp &&
test_grep "1" tmp &&
test_grep ! "2" tmp
'
# The table only sets the numbers beside the code, so a screen reader is told
# to read it as layout rather than as a table of data.
test_expect_success 'the file table is read as layout' '
grep "" tmp
'
# Prints, in stylesheet order, the declarations of the properties named
# second that the rules for the selector named first make, so the last line
# is the one a browser applies wherever the rules stand. A rule names the
# selector when one entry of its list is that selector whole, and a rule
# inside a media query counts like any other.
css_declarations() {
awk -v target="$1" -v properties="$2" '
BEGIN {
RS = "}"
n = split(properties, list, " ")
for (i = 1; i <= n; i++)
wanted[list[i]] = 1
}
{
n = split($0, part, "[{]")
if (n < 2)
next
named = 0
m = split(part[n - 1], selector, ",")
for (i = 1; i <= m; i++) {
s = selector[i]
gsub(/^[ \t\n]+|[ \t\n]+$/, "", s)
if (s == target)
named = 1
}
if (!named)
next
m = split(part[n], declaration, ";")
for (i = 1; i <= m; i++) {
d = declaration[i]
gsub(/^[ \t\n]+|[ \t\n]+$/, "", d)
name = substr(d, 1, index(d, ":") - 1)
if (name in wanted)
print d
}
}
' "$TEST_OUTPUT_DIRECTORY/../assets/cgit.css"
}
# The numbers and the code are two columns of text that a browser keeps in
# step only while both are set in one font. Only a browser shows the drift,
# so this checks the rule that prevents it.
test_expect_success 'the code is set in the font of its line numbers' '
css_declarations "div#cgit table.blob code" "font font-family font-size" >decls &&
tail -n 1 decls >actual &&
echo "font: inherit" >expect &&
test_cmp expect actual
'
# A browser starts a new line at more characters than git ends one at, and
# each would push the rest of the code a line below its numbers.
test_expect_success 'set up a file holding the characters a browser breaks at' '
test_create_repo repos/breaks &&
(
cd repos/breaks &&
printf "a\\rb\\vc\\fd\\302\\205e\\342\\200\\250f\\342\\200\\251g\\nh\\r\\ni\\r" >breaks.txt &&
git add breaks.txt &&
git commit -m breaks
) &&
cgitrc_repo breaksrc breaks enable-blame=1
'
test_expect_success 'each of them is shown as an escape in the tree' '
cgit_ok breaksrc "url=breaks/tree/breaks.txt" >tmp &&
grep "a\\\\rb\\\\vc" tmp &&
grep "c\\\\fd\\\\u0085e" tmp &&
grep "e\\\\u2028f\\\\u2029g$" tmp
'
test_expect_success 'only a carriage return before a line feed is left as it is' '
cgit_ok breaksrc "url=breaks/tree/breaks.txt" >tmp &&
printf "h\\r\\n" >expect &&
grep "^h" tmp >actual &&
test_cmp expect actual &&
grep "^i\\\\r" tmp
'
test_expect_success 'the numbers count only the lines git counts' '
cgit_ok breaksrc "url=breaks/tree/breaks.txt" >tmp &&
test_grep "3" tmp &&
test_grep ! "4" tmp
'
test_expect_success 'the blame page escapes them the same way' '
cgit_ok breaksrc "url=breaks/blame/breaks.txt" >tmp &&
grep "a\\\\rb\\\\vc" tmp &&
grep "c\\\\fd\\\\u0085e" tmp &&
grep "e\\\\u2028f\\\\u2029g$" tmp &&
grep "^i\\\\r" tmp
'
# The striped background is padded with spaces to the widest line, which is
# 31 columns once its six escapes are written out, though it is 18 bytes.
test_expect_success 'the blame stripes are as wide as the escaped line' '
cgit_ok breaksrc "url=breaks/blame/breaks.txt" >tmp &&
grep "^ \\{31\\}" tmp
'
test_expect_success 'blame is refused unless enabled' '
cgit_url "bar/blame/file-1" >tmp &&
test_grep "^Status: 403" tmp &&
test_grep "Blame is disabled
" tmp
'
# Blame takes a file alone, so on its page the crumbs for the folders above
# the file lead to the tree.
test_expect_success 'the folder crumbs of a blame page lead to the tree' '
mkrepo repos/nested 1 &&
(
cd repos/nested &&
mkdir -p a/b &&
echo x >a/b/file.txt &&
git add a &&
git commit -m nested
) &&
cgitrc_repo nestedrc nested enable-blame=1 &&
cgit_ok nestedrc "url=nested/blame/a/b/file.txt" >tmp &&
grep "path: root/a/b/file.txt" tmp &&
cgit_ok nestedrc "url=nested/tree/a" >tmp &&
test_grep "href=./nested/tree/a/b.>b" tmp &&
cgit_ok nestedrc "url=nested/tree/a/b" >tmp &&
test_grep "href=./nested/tree/a/b/file.txt.>file.txt" tmp
'
# Each hash carries a tooltip naming the author, the committer and the
# subject, with the addresses unless enable-plain-email turns them off.
test_expect_success 'a blame hash is titled with its commit' '
cgit_ok nestedrc "url=nested/blame/a/b/file.txt" >tmp &&
test_grep "tmp &&
test_grep "a.txt &&
git add a.txt &&
git commit -m "add a.txt" &&
git mv a.txt b.txt &&
test_seq 1 9 >b.txt &&
echo ten >>b.txt &&
git commit -am "rename a.txt"
) &&
cgitrc_repo renamedrc renamed enable-blame=1 &&
cgit_ok renamedrc "url=renamed/blame/b.txt" >tmp &&
added=$(git -C repos/renamed rev-parse HEAD~1) &&
grep "title=.Blame the previous revision. href=./renamed/blame/a.txt?id=$added.>^" tmp &&
test $(grep -c "Blame the previous revision" tmp) = 1 &&
cgit_ok renamedrc "url=renamed/blame/a.txt&id=$added" >tmp &&
test_grep "plain" tmp &&
test_grep "" tmp
'
test_expect_success 'generate foo+bar/tree' 'cgit_ok_url "foo%2bbar/tree" >tmp'
test_expect_success 'verify a+b link' '
grep "/foo%2Bbar/tree/a%2Bb" tmp
'
test_expect_success 'generate foo+bar/tree?h=1+2' 'cgit_ok_url "foo%2bbar/tree&h=1%2b2" >tmp'
test_expect_success 'verify a+b?h=1+2 link' '
grep "/foo%2Bbar/tree/a%2Bb?h=1%2B2" tmp
'
test_expect_success 'set up a repo with a nested submodule' '
mkrepo repos/submodules 1 &&
(
cd repos/submodules &&
sub=$(git rev-parse HEAD) &&
git update-index --add --cacheinfo 160000,$sub,vendor/sub &&
git commit -m gitlink
) &&
cgitrc_repo submodrc submodules enable-blame=1
'
test_expect_success 'generate submodules/tree/vendor' '
cgit_ok submodrc "url=submodules/tree/vendor" >tmp
'
test_expect_success 'a submodule row shows the entry name alone' '
grep "class=.ls-mod.>sub" tmp &&
test_grep ! ">vendor/sub" tmp
'
test_expect_success 'the submodule hash is titled with the full id' '
sub=$(git -C repos/submodules rev-parse HEAD~1) &&
grep "class=.ls-mod-hash. title=.$sub.>" tmp
'
test_expect_success 'a submodule row shows no size' '
cgit_ok submodrc "url=submodules/tree/vendor" >tmp &&
grep "ls-mod" tmp >row &&
test_grep "class=.ls-size.>-" row
'
test_expect_success 'a submodule row offers log but not blame or plain' '
cgit_ok submodrc "url=submodules/tree/vendor" >tmp &&
grep "ls-mod" tmp >row &&
test_grep ">log" row &&
test_grep ! ">blame" row &&
test_grep ! ">plain" row
'
test_expect_success 'a file row still offers blame' '
cgit_ok submodrc "url=submodules/tree/" >tmp &&
grep ">blame" tmp
'
# A submodule's commit lives in another repository, so its path points there
# the way its row in the listing does.
test_expect_success 'a submodule path is answered with the submodule' '
sub=$(git -C repos/submodules rev-parse HEAD~1) &&
cgit_rc submodrc "url=submodules/tree/vendor/sub" >tmp &&
grep "^Status: 404" tmp &&
grep "This path is a submodule:
sub @
" tmp
'
# A link target inside the tree becomes a link, while an absolute one names a
# file on whatever machine checks the tree out, not a path in the tree, and
# so does one that climbs above the root.
test_expect_success 'only a link target inside the tree is linked' '
test_create_repo repos/links &&
(
cd repos/links &&
echo hi >target.txt &&
git add target.txt &&
rel=$(printf target.txt | git hash-object -w --stdin) &&
abs=$(printf /etc/passwd | git hash-object -w --stdin) &&
up=$(printf ../outside | git hash-object -w --stdin) &&
git update-index --add --cacheinfo 120000,$rel,rel &&
git update-index --add --cacheinfo 120000,$abs,abs &&
git update-index --add --cacheinfo 120000,$up,up &&
git commit -m links
) &&
cgitrc_repo linksrc links &&
cgit_ok linksrc "url=links/tree/" >tmp &&
grep " -> target.txt" tmp &&
grep " -> /etc/passwd" tmp &&
test_grep ! "href=./links/tree/etc/passwd" tmp &&
test_grep " -> \.\./outside" tmp
'
# Blame takes a regular file alone, so a symlink is not offered it. The
# links repository is the one the test above built.
test_expect_success 'a symlink is not offered blame' '
cgitrc_repo linksblamerc links enable-blame=1 &&
cgit_ok linksblamerc "url=links/tree/" >tmp &&
grep "href=./links/blame/target.txt.>blame" tmp &&
test_grep ! "href=./links/blame/rel.>blame" tmp &&
cgit_ok linksblamerc "url=links/tree/rel" >tmp &&
grep "