blob: f1a4b400e9ef62548dfd7dcf9f7306f40d69e5ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/bin/sh

# 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 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
# the pushing user needs write access to it. Nothing here complains or exits
# nonzero when that access is missing, so the pusher is not shown an error
# over a stale page.
#
# To install the hook, copy or link it to hooks/post-receive in each
# repository. Git runs one post-receive per repository, so fold this together
# with the age-file hook to have both.

set -eu

cache_root=/var/cache/cgit
scan_path=/var/lib/git
remove_suffix=0

test -d "$cache_root" || 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