Diff options
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Drop only the cache a push makes stale
The hook cleared every slot on a push to any repository. cgit writes the request a slot answers at its head, so the hook now reads it back and drops only the pushed repository's pages and the index. It finds the repository's url from scan-path or hooks.cgiturl, and with neither it clears everything as before.
Diffstat (limited to '')
-rwxr-xr-xcustom/hooks/post-receive.cgit-cache115
1 file changed, 104 insertions, 11 deletions
diff --git a/custom/hooks/post-receive.cgit-cache b/custom/hooks/post-receive.cgit-cache
index b1d5c13..f1a4b40 100755
--- a/custom/hooks/post-receive.cgit-cache
+++ b/custom/hooks/post-receive.cgit-cache
@@ -1,14 +1,20 @@
#!/bin/sh
-# Drops cgit's output cache after a push, so new commits show up right away
+# Drops the cached pages a push makes stale, so new commits show up right away
# instead of once the cache-*-ttl minutes have passed. It only matters when
# cache-size in cgitrc is above 0, which turns the cache on.
#
-# cgit names each cache slot after a hash of the request url, so there is no
-# way to expire one repository's pages on their own and this clears the lot.
-# cgit refills a slot the next time something asks for it. The cached
-# repository scan goes too, which is what makes a brand new repository appear
-# without waiting out cache-scan-ttl.
+# cgit writes the request each slot answers at the head of the slot file, so
+# the hook reads it back and drops only this repository's pages and the
+# repository index, whose idle column the push changes. Other repositories
+# keep their cached pages. The cached repository scan goes too, which is what
+# makes a brand new repository appear without waiting out cache-scan-ttl.
+#
+# The hook needs the url cgit serves this repository at. A repository that
+# scan-path finds is served at its path below scan_path, as set below, so
+# match that and remove_suffix to cgitrc. One that cgitrc names with repo.url
+# gives its url with "git config hooks.cgiturl <url>". With no url to go on,
+# the whole cache goes, as the safe choice.
#
# The path below is the default cache-root, so a cgitrc that changes the
# setting needs the same change here. The web server owns the cache root, so
@@ -23,11 +29,98 @@
set -eu
cache_root=/var/cache/cgit
+scan_path=/var/lib/git
+remove_suffix=0
test -d "$cache_root" || exit 0
-# Everything directly under the cache root is a cache file, so the only thing
-# worth keeping is the lock files, which belong to writers still working.
-# The "! -name . -prune" pair is how POSIX find stays out of subdirectories.
-find "$cache_root"/. ! -name . -prune ! -name '*.lock' -type f \
- -exec rm -f {} + 2>/dev/null || exit 0
+url=$(git config --get hooks.cgiturl || :)
+scan_root=$(cd "$scan_path" 2>/dev/null && pwd -P) || scan_root=
+if test -z "$url" && test -n "$scan_root"; then
+ git_dir=$(cd "$(git rev-parse --git-dir)" && pwd -P)
+ case $git_dir in
+ "$scan_root"/?*)
+ url=${git_dir#"$scan_root"/}
+ url=${url%/.git}
+ if test "$remove_suffix" = 1; then
+ url=${url%.git}
+ url=${url%/}
+ fi
+ ;;
+ esac
+fi
+
+# Takes one part of a slot's key off the front of $rest into $part. cgit
+# writes each part behind its length and a bar, so no value can be read as
+# another.
+take() {
+ len=${rest%%"|"*}
+ case $len in
+ "" | *[!0-9]*)
+ return 1
+ ;;
+ esac
+ rest=${rest#*"|"}
+ test "${#rest}" -ge "$len" || return 1
+ pattern=
+ while test "${#pattern}" -lt "$len"; do
+ pattern="$pattern?"
+ done
+ part=${rest%"${rest#$pattern}"}
+ rest=${rest#$pattern}
+}
+
+# Whether the page a slot holds goes stale with this push: one of this
+# repository's, by path or by the url and r parameters, or the index. A key
+# that does not read as cgit's counts as stale, since dropping a page costs
+# only a refill.
+stale() {
+ rest=$1
+ take && take && take || return 0
+ path=$part
+ take || return 0
+ case $path in
+ "/$url" | "/$url/"*)
+ return 0
+ ;;
+ "" | "/")
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ case "&$part&" in
+ *"&url=$url&"* | *"&url=$url/"* | *"&r=$url&"*)
+ return 0
+ ;;
+ *"&url=&"* | *"&url=/&"*)
+ ;;
+ *"&url="* | *"&r="*)
+ return 1
+ ;;
+ esac
+ return 0
+}
+
+# The lock files belong to writers still working, so they stay.
+for slot in "$cache_root"/*; do
+ case $slot in
+ *.lock)
+ continue
+ ;;
+ esac
+ test -f "$slot" || continue
+ case $slot in
+ */rc-*)
+ rm -f "$slot" 2>/dev/null || :
+ continue
+ ;;
+ esac
+ if test -z "$url" ||
+ dd if="$slot" bs=4096 count=1 2>/dev/null | tr '\0' '\n' |
+ { IFS= read -r key || :; stale "$key"; }
+ then
+ rm -f "$slot" 2>/dev/null || :
+ fi
+done
+exit 0