From 720051474531025496103898f69e114757efe79b Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Mon, 21 Sep 2026 21:58:47 -1000 Subject: Test the hardening pass --- tests/setup.sh | 18 +- tests/t0002-html-validity.sh | 3 +- tests/t0003-cache.sh | 9 +- tests/t0004-docs.sh | 3 +- tests/t0110-snapshot.sh | 4 +- tests/t0201-filters.sh | 9 +- tests/t0204-limits.sh | 7 +- tests/t0301-security.sh | 2 +- tests/t0302-home-access.sh | 5 +- tests/t0303-robustness.sh | 471 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 501 insertions(+), 30 deletions(-) create mode 100755 tests/t0303-robustness.sh diff --git a/tests/setup.sh b/tests/setup.sh index f84b1b6..a1f2d15 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -49,11 +49,10 @@ TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX" TEST_RESULTS_SAN_DIR="$TEST_RESULTS_BASE.$TEST_RESULTS_SAN_DIR_SFX" -# The library has moved into the trash directory by now, so paths outside -# it are anchored to TEST_OUTPUT_DIRECTORY. - -# The tests run cgit by name, so the binary just built has to come ahead of any -# copy already installed. Under Valgrind the wrappers take that place instead. +# The library has moved into the trash directory by now, so paths outside it +# are anchored to TEST_OUTPUT_DIRECTORY. The tests run cgit by name, so the +# binary just built has to come ahead of any copy already installed. Under +# Valgrind the wrappers take that place instead. if test -n "$cgit_valgrind" then GIT_VALGRIND="$TEST_DIRECTORY/valgrind" @@ -107,8 +106,7 @@ mkrepo() { ) } -setup_repos() -{ +setup_repos() { rm -rf cache mkdir -p cache mkrepo repos/foo 5 >/dev/null @@ -171,13 +169,11 @@ EOF fi } -cgit_query() -{ +cgit_query() { CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="$1" cgit } -cgit_url() -{ +cgit_url() { CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="url=$1" cgit } diff --git a/tests/t0002-html-validity.sh b/tests/t0002-html-validity.sh index 8b04939..f5e918c 100755 --- a/tests/t0002-html-validity.sh +++ b/tests/t0002-html-validity.sh @@ -8,8 +8,7 @@ test_description='Validate html with tidy' . ./setup.sh -test_url() -{ +test_url() { tidy_options="-eq" test -z "$NO_TIDY_WARNINGS" || tidy_options="$tidy_options --show-warnings no" # A second argument names a config of its own, for pages the shared diff --git a/tests/t0003-cache.sh b/tests/t0003-cache.sh index 4693988..d3e448f 100755 --- a/tests/t0003-cache.sh +++ b/tests/t0003-cache.sh @@ -79,8 +79,7 @@ test_expect_success 'set up a repo with a page larger than the output buffer' ' rm -rf cache2 && mkdir cache2 ' -bigpage_query() -{ +bigpage_query() { CGIT_CONFIG="$PWD/bigrc" QUERY_STRING="$1" cgit } @@ -149,9 +148,9 @@ test_expect_success 'set up a repo missing a parent object' ' rm -rf cache4 && mkdir cache4 ' -test_expect_success 'an error after output began replays none of it' ' +test_expect_success 'an error after output began ends the page with it and caches nothing' ' CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/commit/&id=$tip" cgit >broken.out && - grep "Bad commit" broken.out && + grep "Not a commit" broken.out && test $(grep -c "^Status:" broken.out) = 1 && ls cache4 >broken.slots && test_line_count = 0 broken.slots @@ -165,7 +164,7 @@ test_expect_success 'an error page leaves no slot behind' ' rm -rf cache3 && mkdir cache3 && CGIT_CONFIG="$PWD/bigkeyrc" \ QUERY_STRING="url=bigpage/commit/&id=0123456789abcdef0123456789abcdef01234567" cgit >error.out && - grep "Bad commit reference" error.out && + grep "Not a commit" error.out && ls cache3 >error.slots && test_line_count = 0 error.slots ' diff --git a/tests/t0004-docs.sh b/tests/t0004-docs.sh index 65c8275..1eca51e 100755 --- a/tests/t0004-docs.sh +++ b/tests/t0004-docs.sh @@ -48,7 +48,8 @@ test_expect_success 'the manual lists its repository settings in order' ' # The policy is spelled out once per server syntax, so nothing but this check # keeps the three copies from drifting apart. test_expect_success 'the server configs agree on one content security policy' ' - for server in apache lighttpd nginx; do + for server in apache lighttpd nginx + do grep "Content-Security-Policy" "$ROOT/custom/servers/$server.conf" | grep "default-src" | sed "s/.*\"\(default-src[^\"]*\)\".*/\1/" || return 1 diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh index 79fecef..ba38b82 100755 --- a/tests/t0110-snapshot.sh +++ b/tests/t0110-snapshot.sh @@ -72,11 +72,11 @@ test_expect_success 'untar and verify the content' ' test_line_count = 1 master/file-5 ' -test_tar_snapshot tar.gz application/gzip gzip GZIP +test_tar_snapshot tar.gz application/gzip gzip GZIP test_tar_snapshot tar.bz2 application/x-bzip2 bzip2 BZIP2 test_tar_snapshot tar.lz application/x-lzip lzip LZIP test_tar_snapshot tar.xz application/x-xz xz XZ -test_tar_snapshot tar.zst application/zstd zstd ZSTD +test_tar_snapshot tar.zst application/zstd zstd ZSTD if command -v unzip >/dev/null 2>&1 then diff --git a/tests/t0201-filters.sh b/tests/t0201-filters.sh index 8d67c4f..444932a 100755 --- a/tests/t0201-filters.sh +++ b/tests/t0201-filters.sh @@ -69,7 +69,8 @@ test "$CGIT_HAS_LUA" -eq 1 && test_set_prereq CGIT_LUA test "$CGIT_HAS_LUA" -eq 1 || say 'cgit built without lua, error page check skipped' # A die inside a lua filter has to reach the visitor as an error page rather -# than being fed back into the filter that just failed. +# than being fed back into the filter that just failed, and the reason goes to +# the log. test_expect_success CGIT_LUA 'a failing lua filter still renders an error page' ' cat >broken.lua <<-\EOF && function filter_open(...) error("boom") end @@ -82,9 +83,9 @@ test_expect_success CGIT_LUA 'a failing lua filter still renders an error page' echo "repo.path=$PWD/repos/foo/.git" && echo "auth-filter=lua:$PWD/broken.lua" } >brokenrc && - CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=foo/commit" cgit >tmp && - grep "Status: 400" tmp && - grep "Lua error in" tmp + CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=foo/commit" cgit >tmp 2>err && + grep "Status: 500" tmp && + grep "Lua error in" err ' test_done diff --git a/tests/t0204-limits.sh b/tests/t0204-limits.sh index 1195c93..19df797 100755 --- a/tests/t0204-limits.sh +++ b/tests/t0204-limits.sh @@ -30,8 +30,11 @@ test_expect_success 'set up limit fixtures' ' test_seq 301 360 >big.c && printf "int y;\n" >small.c && git commit -am change && - for i in 1 2 3; do git branch branch-$i || exit 1; done && - for i in 1 2 3; do git tag tag-$i || exit 1; done + for i in 1 2 3 + do + git branch branch-$i && + git tag tag-$i || exit 1 + done ) && { echo "virtual-root=/" && diff --git a/tests/t0301-security.sh b/tests/t0301-security.sh index ece60f1..f1af8e9 100755 --- a/tests/t0301-security.sh +++ b/tests/t0301-security.sh @@ -5,7 +5,7 @@ # repository and config that reproduce the original problem and then asks # for the page that used to mishandle it. -test_description='Check security fixes and fork-specific behavior' +test_description='Check security fixes and fork-specific behaviour' . ./setup.sh # Most of what follows shares one repository and one config, so the fixture diff --git a/tests/t0302-home-access.sh b/tests/t0302-home-access.sh index 89aa00f..e0da1f2 100755 --- a/tests/t0302-home-access.sh +++ b/tests/t0302-home-access.sh @@ -24,13 +24,14 @@ strace true 2>/dev/null || { exit } -test_no_home_access () { +test_no_home_access() { # A home that happened to exist would be one git may legitimately # read, leaving the check below with nothing to catch, so extend the # path until nothing is there. missing_home="/path/to/some/place/that/does/not/possibly/exist" depth=0 - while test -d "$missing_home"; do + while test -d "$missing_home" + do depth=$((depth + 1)) missing_home="$missing_home/$depth" done && diff --git a/tests/t0303-robustness.sh b/tests/t0303-robustness.sh new file mode 100755 index 0000000..f7ca26b --- /dev/null +++ b/tests/t0303-robustness.sh @@ -0,0 +1,471 @@ +#!/bin/sh + +# Regression tests from the September 2026 audit. Each case is a config, +# repository or request that used to crash cgit, end it inside git, or hand +# a visitor something other than what was asked for. + +test_description='Check the audit regressions' +. ./setup.sh + +test_expect_success 'set up a repository and a config to vary' ' + mkrepo repos/rob 3 && + sha=$(git -C repos/rob rev-parse HEAD~1) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "snapshots=tar.gz" && + echo "repo.url=rob" && + echo "repo.path=$PWD/repos/rob/.git" + } >robrc +' + +robq() { CGIT_CONFIG="$PWD/robrc" QUERY_STRING="$1" cgit; } + +test_expect_success 'a repository without a path answers 404' ' + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=nopath" && + echo "repo.url=emptypath" && + echo "repo.path=" + } >nopathrc && + for r in nopath emptypath + do + CGIT_CONFIG="$PWD/nopathrc" QUERY_STRING="url=$r/log/" cgit >tmp && + grep "^Status: 404 Not Found" tmp && + grep "Failed to open $r: Not a valid git repository" tmp || return 1 + done +' + +test_expect_success 'a repository with an empty url is skipped with a warning' ' + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=" && + echo "repo.path=$PWD/repos/rob/.git" && + echo "repo.url=rob" && + echo "repo.path=$PWD/repos/rob/.git" + } >nourlrc && + CGIT_CONFIG="$PWD/nourlrc" QUERY_STRING="url=rob/" cgit >tmp 2>err && + grep "^Status: 200" tmp && + grep "Ignoring repository with an empty url" err +' + +test_expect_success 'a scan path that is itself a repository is named after it' ' + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "remove-suffix=1" && + echo "scan-path=$PWD/repos/rob" + } >rootrc && + CGIT_CONFIG="$PWD/rootrc" QUERY_STRING="url=" cgit >tmp && + grep "toplevel-repo.>rob" tmp +' + +test_expect_success 'a repo key after scan-path is reported instead of applied' ' + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=first" && + echo "repo.path=$PWD/repos/rob/.git" && + echo "scan-path=$PWD/repos" && + echo "repo.desc=misplaced" + } >stalerc && + CGIT_CONFIG="$PWD/stalerc" QUERY_STRING="url=" cgit >tmp 2>err && + ! grep "misplaced" tmp && + grep "Ignoring repo.desc before any repo.url" err +' + +test_expect_success 'a valueless or broken git config in a scanned repository is skipped' ' + git clone -q --bare repos/rob/.git scan/a.git && + printf "[cgit]\n\thide\n[cgit\n" >>scan/a.git/config && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "enable-git-config=1" && + echo "scan-path=$PWD/scan" + } >gitcfgrc && + CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp 2>err && + grep "toplevel-repo.>" tmp && + grep "Ignoring unreadable config in $PWD/scan/a.git/config" err +' + +test_expect_success 'only descriptive keys are taken from a scanned repository' ' + git clone -q --bare repos/rob/.git scan/b.git && + { + echo "desc=from the repo" && + echo "readme=master:file-1" && + echo "head-content=" && + echo "logo-link=javascript:alert(2)" + } >scan/b.git/cgitrc && + CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + grep "from the repo" tmp && + grep "pre class=.plaintext." tmp && + ! grep "alert(" tmp && + grep "Ignoring head-content in $PWD/scan/b.git/: trust-scan-config is not set" err && + grep "Ignoring logo-link in " err +' + +test_expect_success 'a filesystem readme from a scanned repository is refused' ' + echo "readme=/etc/hosts" >scan/b.git/cgitrc && + CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + grep "Ignoring readme in " err +' + +test_expect_success SYMLINKS 'a symlink cycle under the scan path is entered once' ' + ln -s . scan/loop && + CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp && + test $(grep -c "toplevel-repo" tmp) = 2 +' + +test_expect_success 'a bare query parameter does not swallow the next one' ' + robq "url=rob/commit/&x&id=$sha" >tmp && + grep "
commit 2" tmp +' + +test_expect_success 'a filter that cannot run answers one error page' ' + { + echo "commit-filter=exec:$PWD/no/such/filter" && + cat robrc + } >badfilterrc && + CGIT_CONFIG="$PWD/badfilterrc" QUERY_STRING="url=rob/commit/" cgit >tmp 2>err && + test $(grep -c "^Status:" tmp) = 1 && + grep "Unable to complete the request" tmp && + grep "Unable to run filter $PWD/no/such/filter" err && + grep "Unable to exec filter" err +' + +test_expect_success 'a filter that exits without reading does not end cgit' ' + { + echo "email-filter=exec:/usr/bin/true" && + cat robrc + } >truerc && + CGIT_CONFIG="$PWD/truerc" QUERY_STRING="url=rob/log/" cgit >tmp && + grep "^Status:" tmp +' + +test_expect_success 'a commit encoding header is read without its newline' ' + ( + cd repos/rob && + echo more >file-1 && + git add file-1 && + git -c i18n.commitEncoding=ISO-8859-1 commit -m "$(printf "caf\351")" + ) && + robq "url=rob/commit/" >tmp && + grep "
caf$(printf "\303\251")<" tmp +' + +test_expect_success 'a submodule below the about path does not end the request' ' + ( + cd repos/rob && + git update-index --add --cacheinfo 160000,$sha,sub && + git commit -m gitlink + ) && + { + echo "mimetype.png=image/png" && + cat robrc && + echo "repo.readme=master:file-1" + } >aboutrc && + CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/sub" cgit >tmp 2>err && + grep "^Status:" tmp && + ! grep "fatal" err +' + +test_expect_success 'a missing image on the about page errors as html' ' + CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/missing.png" cgit >tmp && + grep "^Status: 404" tmp && + grep "^Content-Type: text/html" tmp +' + +test_expect_success 'a snapshot name holding a slash is refused' ' + robq "url=rob/snapshot/:/../../...tar.gz" >tmp && + grep "^Status: 404" tmp && + robq "url=rob/snapshot/rob-master.tar.gz" >tmp && + grep "^Status: 200" tmp +' + +test_expect_success 'a path opening with a colon is refused on the log and patch pages' ' + robq "url=rob/log/:%25x" >tmp && + grep "^Status: 400" tmp && + robq "url=rob/patch/:%25x" >tmp && + grep "^Status: 400" tmp && + test $(grep -c "^Status:" tmp) = 1 +' + +test_expect_success 'a revision spelled like an option is refused' ' + git -C repos/rob update-ref refs/heads/--stdin HEAD && + robq "url=rob/log/&h=--stdin" >tmp && + grep "^Status: 404" tmp && + robq "url=rob/blame/file-1&id=--stdin" >tmp && + grep "^Status: 400" tmp && + robq "url=rob/atom/&h=--stdin" >tmp && + grep "^Status: 404" tmp +' + +test_expect_success 'the blob page reads a file through an annotated tag' ' + git -C repos/rob -c tag.gpgsign=false tag -a -m note ann HEAD && + robq "url=rob/blob/&h=ann&path=file-1" >tmp && + strip_headers body && + printf "more\n" >want && + test_cmp want body +' + +test_expect_success 'the commit page shows a message whole when text follows its trailers' ' + ( + cd repos/rob && + echo again >file-1 && + git add file-1 && + git commit -F - <<-\EOF + Subject + + Body text. + + Signed-off-by: A U Thor + + Conflicts: + file-1 + EOF + ) && + { + echo "enable-trailers=1" && + cat robrc + } >trailrc && + CGIT_CONFIG="$PWD/trailrc" QUERY_STRING="url=rob/commit/" cgit >tmp && + ! grep "commit-trailers" tmp && + grep "Conflicts:" tmp +' + +test_expect_success 'a revision expression that walks the history is refused' ' + robq "url=rob/log/&h=:/zzzz" >tmp && + grep "^Status: 404" tmp && + robq "url=rob/commit/&id=HEAD^{/zzzz}" >tmp && + grep "^Status: 400" tmp && + robq "url=rob/log/&qt=range&q=:/zzzz" >tmp && + grep "^Status: 400" tmp && + robq "url=rob/log/&qt=range&q=master~1..master" >tmp && + grep "^Status: 400" tmp && + robq "url=rob/log/&qt=range&q=$sha..master" >tmp && + grep "^Status: 200" tmp +' + +test_expect_success 'the log search is literal' ' + robq "url=rob/log/&qt=grep&q=commit.1" >tmp && + ! grep ">commit 1" tmp && + robq "url=rob/log/&qt=grep&q=commit%201" >tmp && + grep ">commit 1" tmp +' + +test_expect_success 'a commit with a broken tree line does not crash the blob page' ' + git clone -q repos/rob broken && + ( + cd broken && + bad=$(git cat-file commit HEAD | sed "s/^tree .*/tree not-a-hash/" | + git hash-object -t commit -w --stdin --literally) && + echo $bad >.git/refs/heads/bad + ) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=broken" && + echo "repo.path=$PWD/broken/.git" && + echo "repo.readme=:file-1" + } >brokenrc && + CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/blob/&h=bad&path=file-1" cgit >tmp && + grep "^Status: 404" tmp +' + +test_expect_success 'a die inside git answers one 500 page and logs the reason' ' + printf "[core\n" >>broken/.git/config && + CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/log/" cgit >tmp 2>err && + grep "^Status: 500" tmp && + test $(grep -c "^Status:" tmp) = 1 && + grep "bad config line" err && + ! grep "bad config line" tmp +' + +test_expect_success 'a history with a missing parent renders up to the gap' ' + git clone -q repos/rob gap && + older=$(git -C gap rev-parse HEAD~3) && + parent=$(git -C gap rev-parse HEAD~2) && + rm gap/.git/objects/$(echo $parent | cut -c1-2)/$(echo $parent | cut -c3-) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=gap" && + echo "repo.path=$PWD/gap/.git" + } >gaprc && + CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/log/" cgit >tmp 2>err && + test $(grep -c "^Status:" tmp) = 1 && + grep "Unable to complete the request" tmp && + grep "Failed to traverse parents" err && + CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/patch/&id2=$older" cgit >tmp && + test $(grep -c "^Status:" tmp) = 1 && + grep "^Status: 500" tmp +' + +test_expect_success 'a filter pipeline sees the default SIGPIPE disposition' ' + cat >pipe.sh <<-\EOF && + #!/bin/sh + n=0 + while test $n -lt 20000 + do + echo line + n=$((n + 1)) + done | head -c 5 + EOF + chmod +x pipe.sh && + { + echo "source-filter=exec:$PWD/pipe.sh" && + cat robrc + } >piperc && + CGIT_CONFIG="$PWD/piperc" QUERY_STRING="url=rob/tree/file-1" cgit >tmp 2>err && + grep "^Status: 200" tmp && + ! grep -i "broken pipe" err +' + +test_expect_success 'the fallback branch skips a name spelled like an option' ' + git -C repos/rob update-ref refs/heads/-first HEAD && + git -C repos/rob symbolic-ref HEAD refs/heads/gone && + robq "url=rob/" >tmp && + git -C repos/rob symbolic-ref HEAD refs/heads/master && + grep "^Status: 200" tmp +' + +test_expect_success 'a blank line in the mimetype file is skipped' ' + ( + cd repos/rob && + echo note >note.cgt && + git add note.cgt && + git commit -m note + ) && + printf "\n \nimage/x-cgit cgt\n" >mime.types && + { + echo "mimetype-file=$PWD/mime.types" && + cat robrc + } >mimerc && + CGIT_CONFIG="$PWD/mimerc" QUERY_STRING="url=rob/plain/note.cgt" cgit >tmp && + grep "^Content-Type: image/x-cgit" tmp +' + +test_expect_success 'a refs page named with an id is not cached for ever' ' + mkdir -p cache && + { + echo "cache-size=10" && + echo "cache-root=$PWD/cache" && + echo "cache-static-ttl=-1" && + echo "cache-dynamic-ttl=0" && + echo "cache-summary-ttl=0" && + grep -v "^cache-size" robrc + } >ttlrc && + full=$(git -C repos/rob rev-parse HEAD) && + CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp && + git -C repos/rob tag later HEAD && + CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp && + grep ">later<" tmp +' + +test_expect_success 'the diff caps hold when the path names a directory' ' + ( + cd repos/rob && + mkdir -p dir && + for n in 1 2 3 + do + echo $n >dir/f$n || return 1 + done && + git add dir && + git commit -m dir + ) && + { + echo "max-diff-files=2" && + cat robrc + } >caprc && + CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir" cgit >tmp && + grep "too large to be rendered inline" tmp && + CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir/f1" cgit >tmp && + ! grep "too large to be rendered inline" tmp +' + +test_expect_success 'a snapshot of a tag spelled like an option is archived' ' + git -C repos/rob update-ref refs/tags/-l HEAD && + robq "url=rob/snapshot/-l.tar.gz" >tmp && + grep "^Status: 200" tmp && + strip_headers list && + grep "file-1" list +' + +test -n "$CGIT_VALGRIND" || test_set_prereq NO_VALGRIND + +test_expect_success NO_VALGRIND 'a snapshot format whose compressor is missing answers 500' ' + { + echo "snapshots=tar.xz" && + grep -v "^snapshots" robrc + } >xzrc && + cgitbin=$(command -v cgit) && + mkdir -p nobin && + PATH="$PWD/nobin" CGIT_CONFIG="$PWD/xzrc" \ + QUERY_STRING="url=rob/snapshot/rob-master.tar.xz" $cgitbin >tmp && + grep "^Status: 500" tmp && + test $(grep -c "^Status:" tmp) = 1 +' + +test_expect_success 'a disk readme in the repository directory serves no sibling' ' + echo hello >repos/rob/.git/README && + { + cat robrc && + echo "repo.readme=README" + } >diskrc && + CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/" cgit >tmp && + grep "hello" tmp && + CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/config" cgit >tmp && + ! grep "repositoryformatversion" tmp +' + +test_expect_success 'a tag pointing at a tag links the inner tag by id' ' + git -C repos/rob -c tag.gpgsign=false tag -a -m outer outer ann && + inner=$(git -C repos/rob rev-parse ann) && + robq "url=rob/tag/&id=$inner" >tmp && + grep "^Status: 200" tmp && + grep "tagged object" tmp +' + +test_expect_success 'the dumb transport serves an empty repository' ' + git init -q --bare empty.git && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "repo.url=empty" && + echo "repo.path=$PWD/empty.git" + } >emptyrc && + CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/info/refs" cgit >tmp && + grep "^Status: 200" tmp && + grep "^Content-Type: text/plain" tmp && + CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/objects/info" cgit >tmp && + grep "^Status: 404" tmp +' + +test_expect_success 'a symlink whose target is a large blob is listed without it' ' + big=$(head -c 5000 /dev/zero | tr "\0" a | git -C repos/rob hash-object -w --stdin) && + ( + cd repos/rob && + git update-index --add --cacheinfo 120000,$big,biglink && + git commit -m biglink + ) && + robq "url=rob/tree/" >tmp && + grep "biglink" tmp && + ! grep "aaaaaaaaaa" tmp +' + +test_expect_success 'a chain of single directories is followed a bounded number of levels' ' + ( + cd repos/rob && + deep=$(printf "d/%.0s" $(seq 1 40))leaf && + mkdir -p $(dirname $deep) && + echo x >$deep && + git add d && + git commit -m deep + ) && + robq "url=rob/tree/" >tmp && + test $(grep -o "class=.ls-dir." tmp | wc -l) -le 17 +' + +test_done -- cgit v2.8.0