summary refs log tree commit diff
path: root/www/git.causal.agency
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--www/git.causal.agency/.gitignore6
-rw-r--r--www/git.causal.agency/Makefile20
-rw-r--r--www/git.causal.agency/about-filter.sh15
-rw-r--r--www/git.causal.agency/cgit/.gitignore13
-rw-r--r--www/git.causal.agency/cgit/.mailmap (renamed from .mailmap)0
-rw-r--r--www/git.causal.agency/cgit/AUTHORS (renamed from AUTHORS)0
-rw-r--r--www/git.causal.agency/cgit/COPYING (renamed from COPYING)0
-rw-r--r--www/git.causal.agency/cgit/Makefile (renamed from Makefile)4
-rw-r--r--www/git.causal.agency/cgit/README (renamed from README)13
-rw-r--r--www/git.causal.agency/cgit/cache.c (renamed from cache.c)7
-rw-r--r--www/git.causal.agency/cgit/cache.h (renamed from cache.h)0
-rw-r--r--www/git.causal.agency/cgit/cgit.c (renamed from cgit.c)9
-rw-r--r--www/git.causal.agency/cgit/cgit.css (renamed from cgit.css)28
-rw-r--r--www/git.causal.agency/cgit/cgit.h (renamed from cgit.h)1
-rw-r--r--www/git.causal.agency/cgit/cgit.mk (renamed from cgit.mk)27
-rw-r--r--www/git.causal.agency/cgit/cgit.png (renamed from cgit.png)bin1366 -> 1366 bytes
-rw-r--r--www/git.causal.agency/cgit/cgitrc.5.txt (renamed from cgitrc.5.txt)34
-rw-r--r--www/git.causal.agency/cgit/cmd.c (renamed from cmd.c)0
-rw-r--r--www/git.causal.agency/cgit/cmd.h (renamed from cmd.h)0
-rw-r--r--www/git.causal.agency/cgit/configfile.c (renamed from configfile.c)0
-rw-r--r--www/git.causal.agency/cgit/configfile.h (renamed from configfile.h)0
-rwxr-xr-xwww/git.causal.agency/cgit/contrib/hooks/post-receive.agefile (renamed from contrib/hooks/post-receive.agefile)0
-rw-r--r--www/git.causal.agency/cgit/favicon.ico (renamed from favicon.ico)bin1078 -> 1078 bytes
-rw-r--r--www/git.causal.agency/cgit/filter.c222
-rwxr-xr-xwww/git.causal.agency/cgit/filters/about-formatting.sh (renamed from filters/about-formatting.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/commit-links.sh (renamed from filters/commit-links.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/email-gravatar.py (renamed from filters/email-gravatar.py)3
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/man2html (renamed from filters/html-converters/man2html)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/md2html (renamed from filters/html-converters/md2html)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/rst2html (renamed from filters/html-converters/rst2html)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/txt2html (renamed from filters/html-converters/txt2html)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/syntax-highlighting.py (renamed from filters/syntax-highlighting.py)0
-rwxr-xr-xwww/git.causal.agency/cgit/filters/syntax-highlighting.sh (renamed from filters/syntax-highlighting.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/gen-version.sh (renamed from gen-version.sh)0
-rw-r--r--www/git.causal.agency/cgit/html.c (renamed from html.c)2
-rw-r--r--www/git.causal.agency/cgit/html.h (renamed from html.h)0
-rw-r--r--www/git.causal.agency/cgit/parsing.c (renamed from parsing.c)0
-rw-r--r--www/git.causal.agency/cgit/robots.txt (renamed from robots.txt)1
-rw-r--r--www/git.causal.agency/cgit/scan-tree.c (renamed from scan-tree.c)0
-rw-r--r--www/git.causal.agency/cgit/scan-tree.h (renamed from scan-tree.h)0
-rw-r--r--www/git.causal.agency/cgit/shared.c (renamed from shared.c)0
-rw-r--r--www/git.causal.agency/cgit/tests/.gitignore (renamed from tests/.gitignore)0
-rw-r--r--www/git.causal.agency/cgit/tests/Makefile (renamed from tests/Makefile)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/filters/dump.sh (renamed from tests/filters/dump.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/setup.sh (renamed from tests/setup.sh)19
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh (renamed from tests/t0001-validate-git-versions.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0010-validate-html.sh (renamed from tests/t0010-validate-html.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0020-validate-cache.sh (renamed from tests/t0020-validate-cache.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0101-index.sh (renamed from tests/t0101-index.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0102-summary.sh (renamed from tests/t0102-summary.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0103-log.sh (renamed from tests/t0103-log.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0104-tree.sh (renamed from tests/t0104-tree.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0105-commit.sh (renamed from tests/t0105-commit.sh)6
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0106-diff.sh (renamed from tests/t0106-diff.sh)4
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0107-snapshot.sh (renamed from tests/t0107-snapshot.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0108-patch.sh (renamed from tests/t0108-patch.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0109-gitconfig.sh (renamed from tests/t0109-gitconfig.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0110-rawdiff.sh (renamed from tests/t0110-rawdiff.sh)0
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0111-filter.sh (renamed from tests/t0111-filter.sh)3
-rwxr-xr-xwww/git.causal.agency/cgit/tests/valgrind/bin/cgit (renamed from tests/valgrind/bin/cgit)0
-rw-r--r--www/git.causal.agency/cgit/ui-atom.c (renamed from ui-atom.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-atom.h (renamed from ui-atom.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-blame.c (renamed from ui-blame.c)4
-rw-r--r--www/git.causal.agency/cgit/ui-blame.h (renamed from ui-blame.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-blob.c (renamed from ui-blob.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-blob.h (renamed from ui-blob.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-clone.c (renamed from ui-clone.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-clone.h (renamed from ui-clone.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-commit.c (renamed from ui-commit.c)7
-rw-r--r--www/git.causal.agency/cgit/ui-commit.h (renamed from ui-commit.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-diff.c (renamed from ui-diff.c)30
-rw-r--r--www/git.causal.agency/cgit/ui-diff.h (renamed from ui-diff.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-log.c (renamed from ui-log.c)4
-rw-r--r--www/git.causal.agency/cgit/ui-log.h (renamed from ui-log.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-patch.c (renamed from ui-patch.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-patch.h (renamed from ui-patch.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-plain.c (renamed from ui-plain.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-plain.h (renamed from ui-plain.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-refs.c (renamed from ui-refs.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-refs.h (renamed from ui-refs.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-repolist.c (renamed from ui-repolist.c)4
-rw-r--r--www/git.causal.agency/cgit/ui-repolist.h (renamed from ui-repolist.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-shared.c (renamed from ui-shared.c)44
-rw-r--r--www/git.causal.agency/cgit/ui-shared.h (renamed from ui-shared.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-snapshot.c (renamed from ui-snapshot.c)3
-rw-r--r--www/git.causal.agency/cgit/ui-snapshot.h (renamed from ui-snapshot.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-ssdiff.c (renamed from ui-ssdiff.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-ssdiff.h (renamed from ui-ssdiff.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-stats.c (renamed from ui-stats.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-stats.h (renamed from ui-stats.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-summary.c (renamed from ui-summary.c)0
-rw-r--r--www/git.causal.agency/cgit/ui-summary.h (renamed from ui-summary.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-tag.c (renamed from ui-tag.c)4
-rw-r--r--www/git.causal.agency/cgit/ui-tag.h (renamed from ui-tag.h)0
-rw-r--r--www/git.causal.agency/cgit/ui-tree.c (renamed from ui-tree.c)40
-rw-r--r--www/git.causal.agency/cgit/ui-tree.h (renamed from ui-tree.h)0
-rw-r--r--www/git.causal.agency/cgitrc29
-rw-r--r--www/git.causal.agency/custom.css86
-rw-r--r--www/git.causal.agency/owner-filter.sh6
-rw-r--r--www/git.causal.agency/source-filter.sh25
100 files changed, 551 insertions, 172 deletions
diff --git a/www/git.causal.agency/.gitignore b/www/git.causal.agency/.gitignore
new file mode 100644
index 00000000..a0ae074c
--- /dev/null
+++ b/www/git.causal.agency/.gitignore
@@ -0,0 +1,6 @@
+about-filter
+hilex
+htagml
+mtags
+owner-filter
+source-filter
diff --git a/www/git.causal.agency/Makefile b/www/git.causal.agency/Makefile
new file mode 100644
index 00000000..638c21e7
--- /dev/null
+++ b/www/git.causal.agency/Makefile
@@ -0,0 +1,20 @@
+ETC = /usr/local/etc
+WWW = /usr/local/www/cgit
+LIBEXEC = /usr/local/libexec
+
+BIN = ../../bin
+BINS = about-filter source-filter owner-filter hilex htagml mtags
+
+all: ${BINS}
+
+install: cgitrc custom.css ${BINS}
+	install -m 644 cgitrc ${ETC}
+	install -m 644 custom.css ${WWW}
+	install ${BINS} ${LIBEXEC}
+
+hilex htagml mtags::
+	${MAKE} -C ${BIN} $@
+	ln -f ${BIN}/$@ $@
+
+clean:
+	rm -f ${BINS}
diff --git a/www/git.causal.agency/about-filter.sh b/www/git.causal.agency/about-filter.sh
new file mode 100644
index 00000000..2ff645e2
--- /dev/null
+++ b/www/git.causal.agency/about-filter.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+options=fragment,man=%N.%S,includes=../tree/%I
+
+case "$1" in
+	(README.[1-9])
+		exec /usr/bin/mandoc -T html -O $options
+		;;
+	(*.[1-9])
+		exec /usr/bin/mandoc -T html -O $options,toc
+		;;
+	(*)
+		exec /usr/local/libexec/hilex -l text -f html -o pre
+		;;
+esac
diff --git a/www/git.causal.agency/cgit/.gitignore b/www/git.causal.agency/cgit/.gitignore
new file mode 100644
index 00000000..bca98fcf
--- /dev/null
+++ b/www/git.causal.agency/cgit/.gitignore
@@ -0,0 +1,13 @@
+# Files I don't care to see in git-status/commit
+/cgit
+/git
+cgit.conf
+CGIT-CFLAGS
+VERSION
+cgitrc.5
+cgitrc.5.fo
+cgitrc.5.html
+cgitrc.5.pdf
+cgitrc.5.xml
+*.o
+*.d
diff --git a/.mailmap b/www/git.causal.agency/cgit/.mailmap
index 03b54796..03b54796 100644
--- a/.mailmap
+++ b/www/git.causal.agency/cgit/.mailmap
diff --git a/AUTHORS b/www/git.causal.agency/cgit/AUTHORS
index 031de338..031de338 100644
--- a/AUTHORS
+++ b/www/git.causal.agency/cgit/AUTHORS
diff --git a/COPYING b/www/git.causal.agency/cgit/COPYING
index d159169d..d159169d 100644
--- a/COPYING
+++ b/www/git.causal.agency/cgit/COPYING
diff --git a/Makefile b/www/git.causal.agency/cgit/Makefile
index d13c5bd1..358a3c8e 100644
--- a/Makefile
+++ b/www/git.causal.agency/cgit/Makefile
@@ -15,7 +15,7 @@ pdfdir = $(docdir)
 mandir = $(prefix)/share/man
 SHA1_HEADER = <openssl/sha.h>
 GIT_VER = 2.32.0
-GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
+GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.gz
 INSTALL = install
 COPYTREE = cp -r
 MAN5_TXT = $(wildcard *.5.txt)
@@ -157,7 +157,7 @@ clean-doc:
 	$(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
 
 get-git:
-	curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git
+	curl -L $(GIT_URL) | tar -xzf - && rm -rf git && mv git-$(GIT_VER) git
 
 tags:
 	$(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags
diff --git a/README b/www/git.causal.agency/cgit/README
index 7a6b4a40..371cf21f 100644
--- a/README
+++ b/www/git.causal.agency/cgit/README
@@ -32,18 +32,6 @@ This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You
 can configure this location (and a few other things) by providing a `cgit.conf`
 file (see the Makefile for details).
 
-If you'd like to compile without Lua support, you may use:
-
-    $ make NO_LUA=1
-
-And if you'd like to specify a Lua implementation, you may use:
-
-    $ make LUA_PKGCONFIG=lua5.1
-
-If this is not specified, the Lua implementation will be auto-detected,
-preferring LuaJIT if many are present. Acceptable values are generally "lua",
-"luajit", "lua5.1", and "lua5.2".
-
 
 Dependencies
 ------------
@@ -51,7 +39,6 @@ Dependencies
 * libzip
 * libcrypto (OpenSSL)
 * libssl (OpenSSL)
-* optional: luajit or lua, most reliably used when pkg-config is available
 
 Apache configuration
 --------------------
diff --git a/cache.c b/www/git.causal.agency/cgit/cache.c
index 55199e8f..578b73b0 100644
--- a/cache.c
+++ b/www/git.causal.agency/cgit/cache.c
@@ -265,6 +265,13 @@ static int process_slot(struct cache_slot *slot)
 {
 	int err;
 
+	/*
+	 * Make sure any buffered data is flushed before we redirect,
+	 * do sendfile(2) or write(2)
+	 */
+	if (fflush(stdout))
+		return errno;
+
 	err = open_slot(slot);
 	if (!err && slot->match) {
 		if (is_expired(slot)) {
diff --git a/cache.h b/www/git.causal.agency/cgit/cache.h
index 470da4fc..470da4fc 100644
--- a/cache.h
+++ b/www/git.causal.agency/cgit/cache.h
diff --git a/cgit.c b/www/git.causal.agency/cgit/cgit.c
index 08d81a1d..a86970de 100644
--- a/cgit.c
+++ b/www/git.causal.agency/cgit/cgit.c
@@ -674,7 +674,7 @@ static inline void authenticate_post(void)
 		len = MAX_AUTHENTICATION_POST_BYTES;
 	if ((len = read(STDIN_FILENO, buffer, len)) < 0)
 		die_errno("Could not read POST from stdin");
-	if (write(STDOUT_FILENO, buffer, len) < 0)
+	if (fwrite(buffer, 1, len, stdout) < len)
 		die_errno("Could not write POST to stdout");
 	cgit_close_filter(ctx.cfg.auth_filter);
 	exit(0);
@@ -964,12 +964,6 @@ static void cgit_parse_args(int argc, const char **argv)
 	for (i = 1; i < argc; i++) {
 		if (!strcmp(argv[i], "--version")) {
 			printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION);
-#ifdef NO_LUA
-			printf("[-] ");
-#else
-			printf("[+] ");
-#endif
-			printf("Lua scripting\n");
 #ifndef HAVE_LINUX_SENDFILE
 			printf("[-] ");
 #else
@@ -1051,7 +1045,6 @@ int cmd_main(int argc, const char **argv)
 	const char *path;
 	int err, ttl;
 
-	cgit_init_filters();
 	atexit(cgit_cleanup_filters);
 
 	prepare_context();
diff --git a/cgit.css b/www/git.causal.agency/cgit/cgit.css
index dfa144d0..f3dbb7a9 100644
--- a/cgit.css
+++ b/www/git.causal.agency/cgit/cgit.css
@@ -75,7 +75,7 @@ div#cgit table.tabs td {
 }
 
 div#cgit table.tabs td a {
-	padding: 2px 0.75em;
+	padding: 2px 0.25em;
 	color: #777;
 	font-size: 110%;
 }
@@ -437,11 +437,6 @@ div#cgit div.commit-subject {
 	padding: 0em;
 }
 
-div#cgit div.commit-msg {
-	white-space: pre;
-	font-family: monospace;
-}
-
 div#cgit div.notes-header {
 	font-weight: bold;
 	padding-top: 1.5em;
@@ -538,26 +533,20 @@ div#cgit table.diff {
 	width: 100%;
 }
 
-div#cgit table.diff td {
-	font-family: monospace;
-	white-space: pre;
-}
-
-div#cgit table.diff td div.head {
+div#cgit table.diff td span.head {
 	font-weight: bold;
-	margin-top: 1em;
 	color: black;
 }
 
-div#cgit table.diff td div.hunk {
+div#cgit table.diff td span.hunk {
 	color: #009;
 }
 
-div#cgit table.diff td div.add {
+div#cgit table.diff td span.add {
 	color: green;
 }
 
-div#cgit table.diff td div.del {
+div#cgit table.diff td span.del {
 	color: red;
 }
 
@@ -581,7 +570,6 @@ div#cgit table.list td.reposection {
 
 div#cgit a.button {
 	font-size: 80%;
-	padding: 0em 0.5em;
 }
 
 div#cgit a.primary {
@@ -671,7 +659,6 @@ div#cgit div.footer a:hover {
 
 div#cgit a.branch-deco {
 	color: #000;
-	margin: 0px 0.5em;
 	padding: 0px 0.25em;
 	background-color: #88ff88;
 	border: solid 1px #007700;
@@ -679,7 +666,6 @@ div#cgit a.branch-deco {
 
 div#cgit a.tag-deco {
 	color: #000;
-	margin: 0px 0.5em;
 	padding: 0px 0.25em;
 	background-color: #ffff88;
 	border: solid 1px #777700;
@@ -687,7 +673,6 @@ div#cgit a.tag-deco {
 
 div#cgit a.tag-annotated-deco {
 	color: #000;
-	margin: 0px 0.5em;
 	padding: 0px 0.25em;
 	background-color: #ffcc88;
 	border: solid 1px #777700;
@@ -695,7 +680,6 @@ div#cgit a.tag-annotated-deco {
 
 div#cgit a.remote-deco {
 	color: #000;
-	margin: 0px 0.5em;
 	padding: 0px 0.25em;
 	background-color: #ccccff;
 	border: solid 1px #000077;
@@ -703,7 +687,6 @@ div#cgit a.remote-deco {
 
 div#cgit a.deco {
 	color: #000;
-	margin: 0px 0.5em;
 	padding: 0px 0.25em;
 	background-color: #ff8888;
 	border: solid 1px #770000;
@@ -714,7 +697,6 @@ div#cgit div.commit-subject a.tag-deco,
 div#cgit div.commit-subject a.tag-annotated-deco,
 div#cgit div.commit-subject a.remote-deco,
 div#cgit div.commit-subject a.deco {
-	margin-left: 1em;
 	font-size: 75%;
 }
 
diff --git a/cgit.h b/www/git.causal.agency/cgit/cgit.h
index 69b5c132..72fcd849 100644
--- a/cgit.h
+++ b/www/git.causal.agency/cgit/cgit.h
@@ -385,7 +385,6 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char
 extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
 extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
 extern void cgit_cleanup_filters(void);
-extern void cgit_init_filters(void);
 
 extern void cgit_prepare_repo_env(struct cgit_repo * repo);
 
diff --git a/cgit.mk b/www/git.causal.agency/cgit/cgit.mk
index 3fcc1ca3..5b9ed5be 100644
--- a/cgit.mk
+++ b/www/git.causal.agency/cgit/cgit.mk
@@ -27,32 +27,6 @@ ifdef NO_C99_FORMAT
 	CFLAGS += -DNO_C99_FORMAT
 endif
 
-ifdef NO_LUA
-	LUA_MESSAGE := linking without specified Lua support
-	CGIT_CFLAGS += -DNO_LUA
-else
-ifeq ($(LUA_PKGCONFIG),)
-	LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
-			$(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
-			done)
-	LUA_MODE := autodetected
-else
-	LUA_MODE := specified
-endif
-ifneq ($(LUA_PKGCONFIG),)
-	LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
-	LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
-	LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
-	CGIT_LIBS += $(LUA_LIBS)
-	CGIT_CFLAGS += $(LUA_CFLAGS)
-else
-	LUA_MESSAGE := linking without autodetected Lua support
-	NO_LUA := YesPlease
-	CGIT_CFLAGS += -DNO_LUA
-endif
-
-endif
-
 # Add -ldl to linker flags on systems that commonly use GNU libc.
 ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD))
 	CGIT_LIBS += -ldl
@@ -130,7 +104,6 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
 	$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
 
 $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
-	@echo 1>&1 "    * $(LUA_MESSAGE)"
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
 
 CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
diff --git a/cgit.png b/www/git.causal.agency/cgit/cgit.png
index 425528ee..425528ee 100644
--- a/cgit.png
+++ b/www/git.causal.agency/cgit/cgit.png
Binary files differdiff --git a/cgitrc.5.txt b/www/git.causal.agency/cgit/cgitrc.5.txt
index 33a6a8c0..8d663952 100644
--- a/cgitrc.5.txt
+++ b/www/git.causal.agency/cgit/cgitrc.5.txt
@@ -632,37 +632,6 @@ specification with the relevant string; available values are:
 'exec:'::
 	The default "one process per filter" mode.
 
-'lua:'::
-	Executes the script using a built-in Lua interpreter. The script is
-	loaded once per execution of cgit, and may be called multiple times
-	during cgit's lifetime, making it a good choice for repeated filters
-	such as the 'email filter'. It responds to three functions:
-
-	'filter_open(argument1, argument2, argument3, ...)'::
-		This is called upon activation of the filter for a particular
-		set of data.
-	'filter_write(buffer)'::
-		This is called whenever cgit writes data to the webpage.
-	'filter_close()'::
-		This is called when the current filtering operation is
-		completed. It must return an integer value. Usually 0
-		indicates success.
-
-	Additionally, cgit exposes to the Lua the following built-in functions:
-
-	'html(str)'::
-		Writes 'str' to the webpage.
-	'html_txt(str)'::
-		HTML escapes and writes 'str' to the webpage.
-	'html_attr(str)'::
-		HTML escapes for an attribute and writes "str' to the webpage.
-	'html_url_path(str)'::
-		URL escapes for a path and writes 'str' to the webpage.
-	'html_url_arg(str)'::
-		URL escapes for an argument and writes 'str' to the webpage.
-	'html_include(file)'::
-		Includes 'file' in webpage.
-
 
 Parameters are provided to filters as follows.
 
@@ -696,9 +665,6 @@ auth filter::
 	with a 302 redirect, and write to output one or more "Set-Cookie"
 	HTTP headers, each followed by a newline.
 
-	Please see `filters/simple-authentication.lua` for a clear example
-	script that may be modified.
-
 commit filter::
 	This filter is given no arguments. The commit message text that is to
 	be filtered is available on standard input and the filtered text is
diff --git a/cmd.c b/www/git.causal.agency/cgit/cmd.c
index 0eb75b1d..0eb75b1d 100644
--- a/cmd.c
+++ b/www/git.causal.agency/cgit/cmd.c
diff --git a/cmd.h b/www/git.causal.agency/cgit/cmd.h
index 6249b1d8..6249b1d8 100644
--- a/cmd.h
+++ b/www/git.causal.agency/cgit/cmd.h
diff --git a/configfile.c b/www/git.causal.agency/cgit/configfile.c
index e0391091..e0391091 100644
--- a/configfile.c
+++ b/www/git.causal.agency/cgit/configfile.c
diff --git a/configfile.h b/www/git.causal.agency/cgit/configfile.h
index af7ca197..af7ca197 100644
--- a/configfile.h
+++ b/www/git.causal.agency/cgit/configfile.h
diff --git a/contrib/hooks/post-receive.agefile b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile
index 2f72ae9c..2f72ae9c 100755
--- a/contrib/hooks/post-receive.agefile
+++ b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile
diff --git a/favicon.ico b/www/git.causal.agency/cgit/favicon.ico
index 56ff5938..56ff5938 100644
--- a/favicon.ico
+++ b/www/git.causal.agency/cgit/favicon.ico
Binary files differdiff --git a/www/git.causal.agency/cgit/filter.c b/www/git.causal.agency/cgit/filter.c
new file mode 100644
index 00000000..2b6c838e
--- /dev/null
+++ b/www/git.causal.agency/cgit/filter.c
@@ -0,0 +1,222 @@
+/* filter.c: filter framework functions
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "html.h"
+
+static inline void reap_filter(struct cgit_filter *filter)
+{
+	if (filter && filter->cleanup)
+		filter->cleanup(filter);
+}
+
+void cgit_cleanup_filters(void)
+{
+	int i;
+	reap_filter(ctx.cfg.about_filter);
+	reap_filter(ctx.cfg.commit_filter);
+	reap_filter(ctx.cfg.source_filter);
+	reap_filter(ctx.cfg.email_filter);
+	reap_filter(ctx.cfg.owner_filter);
+	reap_filter(ctx.cfg.auth_filter);
+	for (i = 0; i < cgit_repolist.count; ++i) {
+		reap_filter(cgit_repolist.repos[i].about_filter);
+		reap_filter(cgit_repolist.repos[i].commit_filter);
+		reap_filter(cgit_repolist.repos[i].source_filter);
+		reap_filter(cgit_repolist.repos[i].email_filter);
+		reap_filter(cgit_repolist.repos[i].owner_filter);
+	}
+}
+
+static int open_exec_filter(struct cgit_filter *base, va_list ap)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	int pipe_fh[2];
+	int i;
+
+	for (i = 0; i < filter->base.argument_count; i++)
+		filter->argv[i + 1] = va_arg(ap, char *);
+
+	chk_zero(fflush(stdout), "unable to flush STDOUT");
+	filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
+		"Unable to duplicate STDOUT");
+	chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess");
+	filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
+	if (filter->pid == 0) {
+		close(pipe_fh[1]);
+		chk_non_negative(dup2(pipe_fh[0], STDIN_FILENO),
+			"Unable to use pipe as STDIN");
+		execvp(filter->cmd, filter->argv);
+		die_errno("Unable to exec subprocess %s", filter->cmd);
+	}
+	close(pipe_fh[0]);
+	chk_non_negative(dup2(pipe_fh[1], STDOUT_FILENO),
+		"Unable to use pipe as STDOUT");
+	close(pipe_fh[1]);
+	return 0;
+}
+
+static int close_exec_filter(struct cgit_filter *base)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	int i, exit_status = 0;
+
+	chk_zero(fflush(stdout), "unable to flush STDOUT");
+	chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
+		"Unable to restore STDOUT");
+	close(filter->old_stdout);
+	if (filter->pid < 0)
+		goto done;
+	waitpid(filter->pid, &exit_status, 0);
+	if (WIFEXITED(exit_status))
+		goto done;
+	die("Subprocess %s exited abnormally", filter->cmd);
+
+done:
+	for (i = 0; i < filter->base.argument_count; i++)
+		filter->argv[i + 1] = NULL;
+	return WEXITSTATUS(exit_status);
+
+}
+
+static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
+}
+
+static void cleanup_exec_filter(struct cgit_filter *base)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	if (filter->argv) {
+		free(filter->argv);
+		filter->argv = NULL;
+	}
+	if (filter->cmd) {
+		free(filter->cmd);
+		filter->cmd = NULL;
+	}
+}
+
+static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
+{
+	struct cgit_exec_filter *f;
+	int args_size = 0;
+
+	f = xmalloc(sizeof(*f));
+	/* We leave argv for now and assign it below. */
+	cgit_exec_filter_init(f, xstrdup(cmd), NULL);
+	f->base.argument_count = argument_count;
+	args_size = (2 + argument_count) * sizeof(char *);
+	f->argv = xmalloc(args_size);
+	memset(f->argv, 0, args_size);
+	f->argv[0] = f->cmd;
+	return &f->base;
+}
+
+void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+{
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_exec_filter;
+	filter->base.close = close_exec_filter;
+	filter->base.fprintf = fprintf_exec_filter;
+	filter->base.cleanup = cleanup_exec_filter;
+	filter->cmd = cmd;
+	filter->argv = argv;
+	/* The argument count for open_filter is zero by default, unless called from new_filter, above. */
+	filter->base.argument_count = 0;
+}
+
+int cgit_open_filter(struct cgit_filter *filter, ...)
+{
+	int result;
+	va_list ap;
+	if (!filter)
+		return 0;
+	va_start(ap, filter);
+	result = filter->open(filter, ap);
+	va_end(ap);
+	return result;
+}
+
+int cgit_close_filter(struct cgit_filter *filter)
+{
+	if (!filter)
+		return 0;
+	return filter->close(filter);
+}
+
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+{
+	filter->fprintf(filter, f, prefix);
+}
+
+
+
+static const struct {
+	const char *prefix;
+	struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
+} filter_specs[] = {
+	{ "exec", new_exec_filter },
+};
+
+struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
+{
+	char *colon;
+	int i;
+	size_t len;
+	int argument_count;
+
+	if (!cmd || !cmd[0])
+		return NULL;
+
+	colon = strchr(cmd, ':');
+	len = colon - cmd;
+	/*
+	 * In case we're running on Windows, don't allow a single letter before
+	 * the colon.
+	 */
+	if (len == 1)
+		colon = NULL;
+
+	switch (filtertype) {
+		case AUTH:
+			argument_count = 12;
+			break;
+
+		case EMAIL:
+			argument_count = 2;
+			break;
+
+		case OWNER:
+			argument_count = 0;
+			break;
+
+		case SOURCE:
+		case ABOUT:
+			argument_count = 1;
+			break;
+
+		case COMMIT:
+		default:
+			argument_count = 0;
+			break;
+	}
+
+	/* If no prefix is given, exec filter is the default. */
+	if (!colon)
+		return new_exec_filter(cmd, argument_count);
+
+	for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
+		if (len == strlen(filter_specs[i].prefix) &&
+		    !strncmp(filter_specs[i].prefix, cmd, len))
+			return filter_specs[i].ctor(colon + 1, argument_count);
+	}
+
+	die("Invalid filter type: %.*s", (int) len, cmd);
+}
diff --git a/filters/about-formatting.sh b/www/git.causal.agency/cgit/filters/about-formatting.sh
index 85daf9c2..85daf9c2 100755
--- a/filters/about-formatting.sh
+++ b/www/git.causal.agency/cgit/filters/about-formatting.sh
diff --git a/filters/commit-links.sh b/www/git.causal.agency/cgit/filters/commit-links.sh
index 796ac308..796ac308 100755
--- a/filters/commit-links.sh
+++ b/www/git.causal.agency/cgit/filters/commit-links.sh
diff --git a/filters/email-gravatar.py b/www/git.causal.agency/cgit/filters/email-gravatar.py
index d70440ea..012113c5 100755
--- a/filters/email-gravatar.py
+++ b/www/git.causal.agency/cgit/filters/email-gravatar.py
@@ -1,8 +1,5 @@
 #!/usr/bin/env python3
 
-# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
-# script is very slow, in comparison.
-#
 # This script may be used with the email-filter or repo.email-filter settings in cgitrc.
 #
 # The following environment variables can be used to retrieve the configuration
diff --git a/filters/html-converters/man2html b/www/git.causal.agency/cgit/filters/html-converters/man2html
index 0ef78841..0ef78841 100755
--- a/filters/html-converters/man2html
+++ b/www/git.causal.agency/cgit/filters/html-converters/man2html
diff --git a/filters/html-converters/md2html b/www/git.causal.agency/cgit/filters/html-converters/md2html
index 59f43a84..59f43a84 100755
--- a/filters/html-converters/md2html
+++ b/www/git.causal.agency/cgit/filters/html-converters/md2html
diff --git a/filters/html-converters/rst2html b/www/git.causal.agency/cgit/filters/html-converters/rst2html
index 02d90f81..02d90f81 100755
--- a/filters/html-converters/rst2html
+++ b/www/git.causal.agency/cgit/filters/html-converters/rst2html
diff --git a/filters/html-converters/txt2html b/www/git.causal.agency/cgit/filters/html-converters/txt2html
index 495eeceb..495eeceb 100755
--- a/filters/html-converters/txt2html
+++ b/www/git.causal.agency/cgit/filters/html-converters/txt2html
diff --git a/filters/syntax-highlighting.py b/www/git.causal.agency/cgit/filters/syntax-highlighting.py
index e912594c..e912594c 100755
--- a/filters/syntax-highlighting.py
+++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.py
diff --git a/filters/syntax-highlighting.sh b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh
index 840bc34f..840bc34f 100755
--- a/filters/syntax-highlighting.sh
+++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh
diff --git a/gen-version.sh b/www/git.causal.agency/cgit/gen-version.sh
index 80cf49af..80cf49af 100755
--- a/gen-version.sh
+++ b/www/git.causal.agency/cgit/gen-version.sh
diff --git a/html.c b/www/git.causal.agency/cgit/html.c
index 7f81965f..cefcf5e7 100644
--- a/html.c
+++ b/www/git.causal.agency/cgit/html.c
@@ -80,7 +80,7 @@ char *fmtalloc(const char *format, ...)
 
 void html_raw(const char *data, size_t size)
 {
-	if (write(STDOUT_FILENO, data, size) != size)
+	if (fwrite(data, 1, size, stdout) != size)
 		die_errno("write error on html output");
 }
 
diff --git a/html.h b/www/git.causal.agency/cgit/html.h
index fa4de775..fa4de775 100644
--- a/html.h
+++ b/www/git.causal.agency/cgit/html.h
diff --git a/parsing.c b/www/git.causal.agency/cgit/parsing.c
index 72b59b3c..72b59b3c 100644
--- a/parsing.c
+++ b/www/git.causal.agency/cgit/parsing.c
diff --git a/robots.txt b/www/git.causal.agency/cgit/robots.txt
index 4ce948fe..1b33266d 100644
--- a/robots.txt
+++ b/www/git.causal.agency/cgit/robots.txt
@@ -1,3 +1,4 @@
 User-agent: *
 Disallow: /*/snapshot/*
+Disallow: /*/blame/*
 Allow: /
diff --git a/scan-tree.c b/www/git.causal.agency/cgit/scan-tree.c
index 6a2f65a8..6a2f65a8 100644
--- a/scan-tree.c
+++ b/www/git.causal.agency/cgit/scan-tree.c
diff --git a/scan-tree.h b/www/git.causal.agency/cgit/scan-tree.h
index 1afbd4bb..1afbd4bb 100644
--- a/scan-tree.h
+++ b/www/git.causal.agency/cgit/scan-tree.h
diff --git a/shared.c b/www/git.causal.agency/cgit/shared.c
index 8115469a..8115469a 100644
--- a/shared.c
+++ b/www/git.causal.agency/cgit/shared.c
diff --git a/tests/.gitignore b/www/git.causal.agency/cgit/tests/.gitignore
index 3fd2e965..3fd2e965 100644
--- a/tests/.gitignore
+++ b/www/git.causal.agency/cgit/tests/.gitignore
diff --git a/tests/Makefile b/www/git.causal.agency/cgit/tests/Makefile
index 65e11173..65e11173 100644
--- a/tests/Makefile
+++ b/www/git.causal.agency/cgit/tests/Makefile
diff --git a/tests/filters/dump.sh b/www/git.causal.agency/cgit/tests/filters/dump.sh
index da6f7a1b..da6f7a1b 100755
--- a/tests/filters/dump.sh
+++ b/www/git.causal.agency/cgit/tests/filters/dump.sh
diff --git a/tests/setup.sh b/www/git.causal.agency/cgit/tests/setup.sh
index 8db810ff..31e7d5bb 100755
--- a/tests/setup.sh
+++ b/www/git.causal.agency/cgit/tests/setup.sh
@@ -60,12 +60,6 @@ fi
 
 FILTER_DIRECTORY=$(cd ../filters && pwd)
 
-if cgit --version | grep -F -q "[+] Lua scripting"; then
-	export CGIT_HAS_LUA=1
-else
-	export CGIT_HAS_LUA=0
-fi
-
 mkrepo() {
 	name=$1
 	count=$2
@@ -144,19 +138,6 @@ repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh
 repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh
 repo.readme=master:a+b
 EOF
-
-	if [ $CGIT_HAS_LUA -eq 1 ]; then
-		cat >>cgitrc <<EOF
-repo.url=filter-lua
-repo.path=$PWD/repos/filter/.git
-repo.desc=filtered repo
-repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua
-repo.readme=master:a+b
-EOF
-	fi
 }
 
 cgit_query()
diff --git a/tests/t0001-validate-git-versions.sh b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh
index dd84fe3f..dd84fe3f 100755
--- a/tests/t0001-validate-git-versions.sh
+++ b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh
diff --git a/tests/t0010-validate-html.sh b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh
index ca08d69d..ca08d69d 100755
--- a/tests/t0010-validate-html.sh
+++ b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh
diff --git a/tests/t0020-validate-cache.sh b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh
index 657765d8..657765d8 100755
--- a/tests/t0020-validate-cache.sh
+++ b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh
diff --git a/tests/t0101-index.sh b/www/git.causal.agency/cgit/tests/t0101-index.sh
index 82ef9b04..82ef9b04 100755
--- a/tests/t0101-index.sh
+++ b/www/git.causal.agency/cgit/tests/t0101-index.sh
diff --git a/tests/t0102-summary.sh b/www/git.causal.agency/cgit/tests/t0102-summary.sh
index b8864cb1..b8864cb1 100755
--- a/tests/t0102-summary.sh
+++ b/www/git.causal.agency/cgit/tests/t0102-summary.sh
diff --git a/tests/t0103-log.sh b/www/git.causal.agency/cgit/tests/t0103-log.sh
index bdf1435a..bdf1435a 100755
--- a/tests/t0103-log.sh
+++ b/www/git.causal.agency/cgit/tests/t0103-log.sh
diff --git a/tests/t0104-tree.sh b/www/git.causal.agency/cgit/tests/t0104-tree.sh
index 2e140f59..2e140f59 100755
--- a/tests/t0104-tree.sh
+++ b/www/git.causal.agency/cgit/tests/t0104-tree.sh
diff --git a/tests/t0105-commit.sh b/www/git.causal.agency/cgit/tests/t0105-commit.sh
index 1a12ee39..cfed1e7d 100755
--- a/tests/t0105-commit.sh
+++ b/www/git.causal.agency/cgit/tests/t0105-commit.sh
@@ -11,7 +11,7 @@ test_expect_success 'find commit subject' '
 	grep "<div class=.commit-subject.>commit 5<" tmp
 '
 
-test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp'
+test_expect_success 'find commit msg' 'grep "<pre class=.commit-msg.></pre>" tmp'
 test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp'
 
 test_expect_success 'find diff summary' '
@@ -29,8 +29,8 @@ test_expect_success 'root commit contains diffstat' '
 '
 
 test_expect_success 'root commit contains diff' '
-	grep ">diff --git a/file-1 b/file-1<" tmp &&
-	grep "<div class=.add.>+1</div>" tmp
+	grep ">diff --git a/file-1 b/file-1" tmp &&
+	grep "<span class=.add.>+1</span>" tmp
 '
 
 test_done
diff --git a/tests/t0106-diff.sh b/www/git.causal.agency/cgit/tests/t0106-diff.sh
index 82b645ec..62a0a74a 100755
--- a/tests/t0106-diff.sh
+++ b/www/git.causal.agency/cgit/tests/t0106-diff.sh
@@ -9,11 +9,11 @@ test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp'
 test_expect_success 'find added file' 'grep "new file mode 100644" tmp'
 
 test_expect_success 'find hunk header' '
-	grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp
+	grep "<span class=.hunk.>@@ -0,0 +1 @@</span>" tmp
 '
 
 test_expect_success 'find added line' '
-	grep "<div class=.add.>+5</div>" tmp
+	grep "<span class=.add.>+5</span>" tmp
 '
 
 test_done
diff --git a/tests/t0107-snapshot.sh b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh
index 0811ec40..0811ec40 100755
--- a/tests/t0107-snapshot.sh
+++ b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh
diff --git a/tests/t0108-patch.sh b/www/git.causal.agency/cgit/tests/t0108-patch.sh
index 013d6802..013d6802 100755
--- a/tests/t0108-patch.sh
+++ b/www/git.causal.agency/cgit/tests/t0108-patch.sh
diff --git a/tests/t0109-gitconfig.sh b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh
index 189ef281..189ef281 100755
--- a/tests/t0109-gitconfig.sh
+++ b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh
diff --git a/tests/t0110-rawdiff.sh b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh
index 66fa7d5d..66fa7d5d 100755
--- a/tests/t0110-rawdiff.sh
+++ b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh
diff --git a/tests/t0111-filter.sh b/www/git.causal.agency/cgit/tests/t0111-filter.sh
index 2fdc3669..e5d35750 100755
--- a/tests/t0111-filter.sh
+++ b/www/git.causal.agency/cgit/tests/t0111-filter.sh
@@ -4,9 +4,6 @@ test_description='Check filtered content'
 . ./setup.sh
 
 prefixes="exec"
-if [ $CGIT_HAS_LUA -eq 1 ]; then
-	prefixes="$prefixes lua"
-fi
 
 for prefix in $prefixes
 do
diff --git a/tests/valgrind/bin/cgit b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit
index dcdfbe53..dcdfbe53 100755
--- a/tests/valgrind/bin/cgit
+++ b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit
diff --git a/ui-atom.c b/www/git.causal.agency/cgit/ui-atom.c
index 1056f363..1056f363 100644
--- a/ui-atom.c
+++ b/www/git.causal.agency/cgit/ui-atom.c
diff --git a/ui-atom.h b/www/git.causal.agency/cgit/ui-atom.h
index dda953bb..dda953bb 100644
--- a/ui-atom.h
+++ b/www/git.causal.agency/cgit/ui-atom.h
diff --git a/ui-blame.c b/www/git.causal.agency/cgit/ui-blame.c
index 03136f78..4adec2b9 100644
--- a/ui-blame.c
+++ b/www/git.causal.agency/cgit/ui-blame.c
@@ -152,6 +152,10 @@ static void print_object(const struct object_id *oid, const char *path,
 	cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
 	html(")\n");
 
+	if (buffer_is_binary(buf, size)) {
+		html("<div class='error'>blob is binary.</div>");
+		goto cleanup;
+	}
 	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
 		htmlf("<div class='error'>blob size (%ldKB)"
 		      " exceeds display size limit (%dKB).</div>",
diff --git a/ui-blame.h b/www/git.causal.agency/cgit/ui-blame.h
index 5b97e035..5b97e035 100644
--- a/ui-blame.h
+++ b/www/git.causal.agency/cgit/ui-blame.h
diff --git a/ui-blob.c b/www/git.causal.agency/cgit/ui-blob.c
index c10ae42e..c10ae42e 100644
--- a/ui-blob.c
+++ b/www/git.causal.agency/cgit/ui-blob.c
diff --git a/ui-blob.h b/www/git.causal.agency/cgit/ui-blob.h
index 16847b20..16847b20 100644
--- a/ui-blob.h
+++ b/www/git.causal.agency/cgit/ui-blob.h
diff --git a/ui-clone.c b/www/git.causal.agency/cgit/ui-clone.c
index 5dccb639..5dccb639 100644
--- a/ui-clone.c
+++ b/www/git.causal.agency/cgit/ui-clone.c
diff --git a/ui-clone.h b/www/git.causal.agency/cgit/ui-clone.h
index 3e460a3d..3e460a3d 100644
--- a/ui-clone.h
+++ b/www/git.causal.agency/cgit/ui-clone.h
diff --git a/ui-commit.c b/www/git.causal.agency/cgit/ui-commit.c
index 948118c4..b49259e6 100644
--- a/ui-commit.c
+++ b/www/git.causal.agency/cgit/ui-commit.c
@@ -39,10 +39,11 @@ void cgit_print_commit(char *hex, const char *prefix)
 	}
 	info = cgit_parse_commit(commit);
 
-	format_display_notes(&oid, &notes, PAGE_ENCODING, 0);
+	format_display_notes(&oid, &notes, PAGE_ENCODING, 1);
 
 	load_ref_decorations(NULL, DECORATE_FULL_REFS);
 
+	ctx.page.title = fmtalloc("%s - %s", info->subject, ctx.page.title);
 	cgit_print_layout_start();
 	cgit_print_diff_ctrls();
 	html("<table summary='commit info' class='commit-info'>\n");
@@ -120,11 +121,11 @@ void cgit_print_commit(char *hex, const char *prefix)
 	cgit_close_filter(ctx.repo->commit_filter);
 	show_commit_decorations(commit);
 	html("</div>");
-	html("<div class='commit-msg'>");
+	html("<pre class='commit-msg'>");
 	cgit_open_filter(ctx.repo->commit_filter);
 	html_txt(info->msg);
 	cgit_close_filter(ctx.repo->commit_filter);
-	html("</div>");
+	html("</pre>");
 	if (notes.len != 0) {
 		html("<div class='notes-header'>Notes</div>");
 		html("<div class='notes'>");
diff --git a/ui-commit.h b/www/git.causal.agency/cgit/ui-commit.h
index 8198b4ba..8198b4ba 100644
--- a/ui-commit.h
+++ b/www/git.causal.agency/cgit/ui-commit.h
diff --git a/ui-diff.c b/www/git.causal.agency/cgit/ui-diff.c
index 5ed5990c..2a64ae8f 100644
--- a/ui-diff.c
+++ b/www/git.causal.agency/cgit/ui-diff.c
@@ -231,11 +231,11 @@ static void print_line(char *line, int len)
 	else if (line[0] == '@')
 		class = "hunk";
 
-	htmlf("<div class='%s'>", class);
+	htmlf("<span class='%s'>", class);
 	line[len-1] = '\0';
 	html_txt(line);
-	html("</div>");
 	line[len-1] = c;
+	html("</span>\n");
 }
 
 static void header(const struct object_id *oid1, char *path1, int mode1,
@@ -245,22 +245,23 @@ static void header(const struct object_id *oid1, char *path1, int mode1,
 	int subproject;
 
 	subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
-	html("<div class='head'>");
+	html("<span class='head'>");
 	html("diff --git a/");
 	html_txt(path1);
 	html(" b/");
 	html_txt(path2);
+	html("\n");
 
 	if (mode1 == 0)
-		htmlf("<br/>new file mode %.6o", mode2);
+		htmlf("new file mode %.6o\n", mode2);
 
 	if (mode2 == 0)
-		htmlf("<br/>deleted file mode %.6o", mode1);
+		htmlf("deleted file mode %.6o\n", mode1);
 
 	if (!subproject) {
 		abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV));
 		abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV));
-		htmlf("<br/>index %s..%s", abbrev1, abbrev2);
+		htmlf("index %s..%s", abbrev1, abbrev2);
 		free(abbrev1);
 		free(abbrev2);
 		if (mode1 != 0 && mode2 != 0) {
@@ -268,28 +269,31 @@ static void header(const struct object_id *oid1, char *path1, int mode1,
 			if (mode2 != mode1)
 				htmlf("..%.6o", mode2);
 		}
+		html("\n");
 		if (is_null_oid(oid1)) {
 			path1 = "dev/null";
-			html("<br/>--- /");
+			html("--- /");
 		} else
-			html("<br/>--- a/");
+			html("--- a/");
 		if (mode1 != 0)
 			cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
 				       oid_to_hex(old_rev_oid), path1);
 		else
 			html_txt(path1);
+		html("\n");
 		if (is_null_oid(oid2)) {
 			path2 = "dev/null";
-			html("<br/>+++ /");
+			html("+++ /");
 		} else
-			html("<br/>+++ b/");
+			html("+++ b/");
 		if (mode2 != 0)
 			cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
 				       oid_to_hex(new_rev_oid), path2);
 		else
 			html_txt(path2);
+		html("\n");
 	}
-	html("</div>");
+	html("</span>");
 }
 
 static void filepair_cb(struct diff_filepair *pair)
@@ -488,12 +492,12 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
 		html("<table summary='ssdiff' class='ssdiff'>");
 	} else {
 		html("<table summary='diff' class='diff'>");
-		html("<tr><td>");
+		html("<tr><td><pre>");
 	}
 	cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix,
 		       ctx.qry.ignorews);
 	if (!use_ssdiff)
-		html("</td></tr>");
+		html("</pre></td></tr>");
 	html("</table>");
 
 	if (show_ctrls)
diff --git a/ui-diff.h b/www/git.causal.agency/cgit/ui-diff.h
index 39264a16..39264a16 100644
--- a/ui-diff.h
+++ b/www/git.causal.agency/cgit/ui-diff.h
diff --git a/ui-log.c b/www/git.causal.agency/cgit/ui-log.c
index 20774bf8..b443ca73 100644
--- a/ui-log.c
+++ b/www/git.causal.agency/cgit/ui-log.c
@@ -75,11 +75,13 @@ void show_commit_decorations(struct commit *commit)
 			 * don't display anything. */
 			break;
 		case DECORATION_REF_LOCAL:
+			html(" ");
 			cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
 				ctx.qry.vpath, 0, NULL, NULL,
 				ctx.qry.showmsg, 0);
 			break;
 		case DECORATION_REF_TAG:
+			html(" ");
 			if (!read_ref(deco->name, &oid_tag) && !peel_iterated_oid(&oid_tag, &peeled))
 				is_annotated = !oideq(&oid_tag, &peeled);
 			cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf);
@@ -87,12 +89,14 @@ void show_commit_decorations(struct commit *commit)
 		case DECORATION_REF_REMOTE:
 			if (!ctx.repo->enable_remote_branches)
 				break;
+			html(" ");
 			cgit_log_link(buf, NULL, "remote-deco", NULL,
 				oid_to_hex(&commit->object.oid),
 				ctx.qry.vpath, 0, NULL, NULL,
 				ctx.qry.showmsg, 0);
 			break;
 		default:
+			html(" ");
 			cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
 					oid_to_hex(&commit->object.oid),
 					ctx.qry.vpath);
diff --git a/ui-log.h b/www/git.causal.agency/cgit/ui-log.h
index 325607cd..325607cd 100644
--- a/ui-log.h
+++ b/www/git.causal.agency/cgit/ui-log.h
diff --git a/ui-patch.c b/www/git.causal.agency/cgit/ui-patch.c
index 4ac03cbe..4ac03cbe 100644
--- a/ui-patch.c
+++ b/www/git.causal.agency/cgit/ui-patch.c
diff --git a/ui-patch.h b/www/git.causal.agency/cgit/ui-patch.h
index 7a6cacd5..7a6cacd5 100644
--- a/ui-patch.h
+++ b/www/git.causal.agency/cgit/ui-patch.h
diff --git a/ui-plain.c b/www/git.causal.agency/cgit/ui-plain.c
index 65a205fa..65a205fa 100644
--- a/ui-plain.c
+++ b/www/git.causal.agency/cgit/ui-plain.c
diff --git a/ui-plain.h b/www/git.causal.agency/cgit/ui-plain.h
index 5bff07b8..5bff07b8 100644
--- a/ui-plain.h
+++ b/www/git.causal.agency/cgit/ui-plain.h
diff --git a/ui-refs.c b/www/git.causal.agency/cgit/ui-refs.c
index 456f610d..456f610d 100644
--- a/ui-refs.c
+++ b/www/git.causal.agency/cgit/ui-refs.c
diff --git a/ui-refs.h b/www/git.causal.agency/cgit/ui-refs.h
index 1d4a54a2..1d4a54a2 100644
--- a/ui-refs.h
+++ b/www/git.causal.agency/cgit/ui-refs.h
diff --git a/ui-repolist.c b/www/git.causal.agency/cgit/ui-repolist.c
index 529a2038..97b11c5f 100644
--- a/ui-repolist.c
+++ b/www/git.causal.agency/cgit/ui-repolist.c
@@ -321,7 +321,7 @@ void cgit_print_repolist(void)
 		}
 		htmlf("<tr><td class='%s'>",
 		      !sorted && section ? "sublevel-repo" : "toplevel-repo");
-		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
+		cgit_summary_link(ctx.repo->name, NULL, NULL, NULL);
 		html("</td><td>");
 		repourl = cgit_repourl(ctx.repo->url);
 		html_link_open(repourl, NULL, NULL);
@@ -353,8 +353,10 @@ void cgit_print_repolist(void)
 		if (ctx.cfg.enable_index_links) {
 			html("<td>");
 			cgit_summary_link("summary", NULL, "button", NULL);
+			html(" ");
 			cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
 				      0, NULL, NULL, ctx.qry.showmsg, 0);
+			html(" ");
 			cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
 			html("</td>");
 		}
diff --git a/ui-repolist.h b/www/git.causal.agency/cgit/ui-repolist.h
index 1b6b3227..1b6b3227 100644
--- a/ui-repolist.h
+++ b/www/git.causal.agency/cgit/ui-repolist.h
diff --git a/ui-shared.c b/www/git.causal.agency/cgit/ui-shared.c
index acd8ab55..dfaf5952 100644
--- a/ui-shared.c
+++ b/www/git.causal.agency/cgit/ui-shared.c
@@ -835,7 +835,7 @@ void cgit_print_docend(void)
 	if (ctx.cfg.footer)
 		html_include(ctx.cfg.footer);
 	else {
-		htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> "
+		htmlf("<div class='footer'>generated by <a href='https://git.causal.agency/src/log/www/git.causal.agency/cgit'>cgit %s</a> "
 			"(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string);
 		html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601)));
 		html("</div>\n");
@@ -894,6 +894,15 @@ void cgit_add_clone_urls(void (*fn)(const char *))
 		add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url);
 }
 
+static int print_this_commit_option(void)
+{
+	struct object_id oid;
+	if (!ctx.qry.head || get_oid(ctx.qry.head, &oid))
+		return 1;
+	html_option(oid_to_hex(&oid), "this commit", ctx.qry.head);
+	return 0;
+}
+
 static int print_branch_option(const char *refname, const struct object_id *oid,
 			       int flags, void *cb_data)
 {
@@ -995,15 +1004,18 @@ static void print_header(void)
 	if (ctx.repo) {
 		cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1);
 		html(" : ");
-		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
+		cgit_summary_link(ctx.repo->name, NULL, NULL, NULL);
 		if (ctx.env.authenticated) {
 			html("</td><td class='form'>");
 			html("<form method='get'>\n");
 			cgit_add_hidden_formfields(0, 1, ctx.qry.page);
 			html("<select name='h' onchange='this.form.submit();'>\n");
+			print_this_commit_option();
+			html("<optgroup label='branches'>");
 			for_each_branch_ref(print_branch_option, ctx.qry.head);
 			if (ctx.repo->enable_remote_branches)
 				for_each_remote_ref(print_branch_option, ctx.qry.head);
+			html("</optgroup>");
 			html("</select> ");
 			html("<input type='submit' value='switch'/>");
 			html("</form>");
@@ -1016,7 +1028,13 @@ static void print_header(void)
 	if (ctx.repo) {
 		html_txt(ctx.repo->desc);
 		html("</td><td class='sub right'>");
-		html_txt(ctx.repo->owner);
+		if (ctx.repo->owner_filter) {
+			cgit_open_filter(ctx.repo->owner_filter);
+			html_txt(ctx.repo->owner);
+			cgit_close_filter(ctx.repo->owner_filter);
+		} else {
+			html_txt(ctx.repo->owner);
+		}
 	} else {
 		if (ctx.cfg.root_desc)
 			html_txt(ctx.cfg.root_desc);
@@ -1032,32 +1050,41 @@ void cgit_print_pageheader(void)
 
 	html("<table class='tabs'><tr><td>\n");
 	if (ctx.env.authenticated && ctx.repo) {
-		if (ctx.repo->readme.nr)
+		if (ctx.repo->readme.nr) {
 			reporevlink("about", "about", NULL,
 				    hc("about"), ctx.qry.head, NULL,
 				    NULL);
+			html(" ");
+		}
 		cgit_summary_link("summary", NULL, hc("summary"),
 				  ctx.qry.head);
+		html(" ");
 		cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head,
 			       ctx.qry.oid, NULL);
+		html(" ");
 		cgit_log_link("log", NULL, hc("log"), ctx.qry.head,
 			      NULL, ctx.qry.vpath, 0, NULL, NULL,
 			      ctx.qry.showmsg, ctx.qry.follow);
+		html(" ");
 		if (ctx.qry.page && !strcmp(ctx.qry.page, "blame"))
 			cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head,
 				        ctx.qry.oid, ctx.qry.vpath);
 		else
 			cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head,
 				       ctx.qry.oid, ctx.qry.vpath);
+		html(" ");
 		cgit_commit_link("commit", NULL, hc("commit"),
 				 ctx.qry.head, ctx.qry.oid, ctx.qry.vpath);
+		html(" ");
 		cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head,
 			       ctx.qry.oid, ctx.qry.oid2, ctx.qry.vpath);
-		if (ctx.repo->max_stats)
+		if (ctx.repo->max_stats) {
+			html(" ");
 			cgit_stats_link("stats", NULL, hc("stats"),
 					ctx.qry.head, ctx.qry.vpath);
+		}
 		if (ctx.repo->homepage) {
-			html("<a href='");
+			html(" <a href='");
 			html_attr(ctx.repo->homepage);
 			html("'>homepage</a>");
 		}
@@ -1201,9 +1228,12 @@ void cgit_set_title_from_path(const char *path)
 	if (!path)
 		return;
 
-	for (last_slash = path + strlen(path); (slash = memrchr(path, '/', last_slash - path)) != NULL; last_slash = slash) {
+	last_slash = path + strlen(path);
+	for (slash = last_slash; slash > path; --slash) {
+		if (*slash != '/') continue;
 		strbuf_add(&sb, slash + 1, last_slash - slash - 1);
 		strbuf_addstr(&sb, " \xc2\xab ");
+		last_slash = slash;
 	}
 	strbuf_add(&sb, path, last_slash - path);
 	strbuf_addf(&sb, " - %s", ctx.page.title);
diff --git a/ui-shared.h b/www/git.causal.agency/cgit/ui-shared.h
index 6964873a..6964873a 100644
--- a/ui-shared.h
+++ b/www/git.causal.agency/cgit/ui-shared.h
diff --git a/ui-snapshot.c b/www/git.causal.agency/cgit/ui-snapshot.c
index 18361a65..28013935 100644
--- a/ui-snapshot.c
+++ b/www/git.causal.agency/cgit/ui-snapshot.c
@@ -37,6 +37,9 @@ static int write_archive_type(const char *format, const char *hex, const char *p
 	/* strvec guarantees a trailing NULL entry. */
 	memcpy(nargv, argv.v, sizeof(char *) * (argv.nr + 1));
 
+	if (fflush(stdout))
+		return errno;
+
 	result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0);
 	strvec_clear(&argv);
 	free(nargv);
diff --git a/ui-snapshot.h b/www/git.causal.agency/cgit/ui-snapshot.h
index a8deec36..a8deec36 100644
--- a/ui-snapshot.h
+++ b/www/git.causal.agency/cgit/ui-snapshot.h
diff --git a/ui-ssdiff.c b/www/git.causal.agency/cgit/ui-ssdiff.c
index af8bc9e0..af8bc9e0 100644
--- a/ui-ssdiff.c
+++ b/www/git.causal.agency/cgit/ui-ssdiff.c
diff --git a/ui-ssdiff.h b/www/git.causal.agency/cgit/ui-ssdiff.h
index 11f27144..11f27144 100644
--- a/ui-ssdiff.h
+++ b/www/git.causal.agency/cgit/ui-ssdiff.h
diff --git a/ui-stats.c b/www/git.causal.agency/cgit/ui-stats.c
index 09b3625e..09b3625e 100644
--- a/ui-stats.c
+++ b/www/git.causal.agency/cgit/ui-stats.c
diff --git a/ui-stats.h b/www/git.causal.agency/cgit/ui-stats.h
index 0e61b03d..0e61b03d 100644
--- a/ui-stats.h
+++ b/www/git.causal.agency/cgit/ui-stats.h
diff --git a/ui-summary.c b/www/git.causal.agency/cgit/ui-summary.c
index 947812a8..947812a8 100644
--- a/ui-summary.c
+++ b/www/git.causal.agency/cgit/ui-summary.c
diff --git a/ui-summary.h b/www/git.causal.agency/cgit/ui-summary.h
index cba696af..cba696af 100644
--- a/ui-summary.h
+++ b/www/git.causal.agency/cgit/ui-summary.h
diff --git a/ui-tag.c b/www/git.causal.agency/cgit/ui-tag.c
index 424bbccd..05952429 100644
--- a/ui-tag.c
+++ b/www/git.causal.agency/cgit/ui-tag.c
@@ -25,9 +25,9 @@ static void print_tag_content(char *buf)
 	html_txt(buf);
 	html("</div>");
 	if (p) {
-		html("<div class='commit-msg'>");
+		html("<pre class='commit-msg'>");
 		html_txt(++p);
-		html("</div>");
+		html("</pre>");
 	}
 }
 
diff --git a/ui-tag.h b/www/git.causal.agency/cgit/ui-tag.h
index d295cdcd..d295cdcd 100644
--- a/ui-tag.h
+++ b/www/git.causal.agency/cgit/ui-tag.h
diff --git a/ui-tree.c b/www/git.causal.agency/cgit/ui-tree.c
index b61f6f54..21e0b884 100644
--- a/ui-tree.c
+++ b/www/git.causal.agency/cgit/ui-tree.c
@@ -89,6 +89,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch
 	enum object_type type;
 	char *buf;
 	unsigned long size;
+	int is_binary;
 
 	type = oid_object_info(the_repository, oid, &size);
 	if (type == OBJ_BAD) {
@@ -103,6 +104,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch
 			"Error reading object %s", oid_to_hex(oid));
 		return;
 	}
+	is_binary = buffer_is_binary(buf, size);
 
 	cgit_set_title_from_path(path);
 
@@ -110,7 +112,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch
 	htmlf("blob: %s (", oid_to_hex(oid));
 	cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
 		        rev, path);
-	if (ctx.repo->enable_blame) {
+	if (ctx.repo->enable_blame && !is_binary) {
 		html(") (");
 		cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
 			        rev, path);
@@ -123,7 +125,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch
 		return;
 	}
 
-	if (buffer_is_binary(buf, size))
+	if (is_binary)
 		print_binary_buffer(buf, size);
 	else
 		print_text_buffer(basename, buf, size);
@@ -202,9 +204,11 @@ static int ls_item(const struct object_id *oid, struct strbuf *base,
 	struct walk_tree_context *walk_tree_ctx = cbdata;
 	char *name;
 	struct strbuf fullpath = STRBUF_INIT;
+	struct strbuf linkpath = STRBUF_INIT;
 	struct strbuf class = STRBUF_INIT;
 	enum object_type type;
 	unsigned long size = 0;
+	char *buf;
 
 	name = xstrdup(pathname);
 	strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
@@ -216,8 +220,7 @@ static int ls_item(const struct object_id *oid, struct strbuf *base,
 			htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
 			      name,
 			      oid_to_hex(oid));
-			free(name);
-			return 0;
+			goto cleanup;
 		}
 	}
 
@@ -237,22 +240,45 @@ static int ls_item(const struct object_id *oid, struct strbuf *base,
 		cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
 			       walk_tree_ctx->curr_rev, fullpath.buf);
 	}
+	if (S_ISLNK(mode)) {
+		html(" -> ");
+		buf = read_object_file(oid, &type, &size);
+		if (!buf) {
+			htmlf("Error reading object: %s", oid_to_hex(oid));
+			goto cleanup;
+		}
+		strbuf_addbuf(&linkpath, &fullpath);
+		strbuf_addf(&linkpath, "/../%s", buf);
+		strbuf_normalize_path(&linkpath);
+		cgit_tree_link(buf, NULL, class.buf, ctx.qry.head,
+			walk_tree_ctx->curr_rev, linkpath.buf);
+		free(buf);
+		strbuf_release(&linkpath);
+	}
 	htmlf("</td><td class='ls-size'>%li</td>", size);
 
 	html("<td>");
 	cgit_log_link("log", NULL, "button", ctx.qry.head,
 		      walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
 		      ctx.qry.showmsg, 0);
-	if (ctx.repo->max_stats)
+	if (ctx.repo->max_stats) {
+		html(" ");
 		cgit_stats_link("stats", NULL, "button", ctx.qry.head,
 				fullpath.buf);
-	if (!S_ISGITLINK(mode))
+	}
+	if (!S_ISGITLINK(mode)) {
+		html(" ");
 		cgit_plain_link("plain", NULL, "button", ctx.qry.head,
 				walk_tree_ctx->curr_rev, fullpath.buf);
-	if (!S_ISDIR(mode) && ctx.repo->enable_blame)
+	}
+	if (!S_ISDIR(mode) && ctx.repo->enable_blame) {
+		html(" ");
 		cgit_blame_link("blame", NULL, "button", ctx.qry.head,
 				walk_tree_ctx->curr_rev, fullpath.buf);
+	}
 	html("</td></tr>\n");
+
+cleanup:
 	free(name);
 	strbuf_release(&fullpath);
 	strbuf_release(&class);
diff --git a/ui-tree.h b/www/git.causal.agency/cgit/ui-tree.h
index bbd34e35..bbd34e35 100644
--- a/ui-tree.h
+++ b/www/git.causal.agency/cgit/ui-tree.h
diff --git a/www/git.causal.agency/cgitrc b/www/git.causal.agency/cgitrc
new file mode 100644
index 00000000..8ccd7c72
--- /dev/null
+++ b/www/git.causal.agency/cgitrc
@@ -0,0 +1,29 @@
+root-title=causal agency
+root-desc=“I think some people from the Gentoo project are behind this.”
+logo=
+
+clone-url=https://$HTTP_HOST/$CGIT_REPO_URL
+snapshots=tar.gz zip
+
+enable-blame=1
+enable-commit-graph=1
+enable-subject-links=1
+enable-follow-links=1
+enable-index-owner=0
+repository-sort=age
+branch-sort=age
+
+css=/custom.css
+email-filter=/usr/local/libexec/cgit-email
+about-filter=/usr/local/libexec/about-filter
+source-filter=/usr/local/libexec/source-filter
+owner-filter=/usr/local/libexec/owner-filter
+
+readme=:README.7
+readme=:README
+
+remove-suffix=1
+enable-git-config=1
+scan-path=/home/june/pub
+
+cache-size=1024
diff --git a/www/git.causal.agency/custom.css b/www/git.causal.agency/custom.css
new file mode 100644
index 00000000..3bc61c90
--- /dev/null
+++ b/www/git.causal.agency/custom.css
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+@import url("cgit.css");
+
+* { line-height: 1.25em; }
+
+div#cgit {
+	max-width: 117ch;
+	margin: auto;
+	font-family: monospace;
+	-moz-tab-size: 4;
+	tab-size: 4;
+}
+
+div#cgit table#header td.sub {
+	border-top: none;
+}
+div#cgit table#header td.sub.right {
+	padding-right: 1em;
+}
+div#cgit table.tabs {
+	border-bottom: none;
+}
+div#cgit div.content {
+	border-bottom: none;
+}
+div#cgit table.list th a {
+	color: inherit;
+}
+div#cgit table.list tr:nth-child(even) {
+	background: inherit;
+}
+div#cgit table.list tr:hover {
+	background: inherit;
+}
+div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
+	background: inherit;
+}
+
+div#cgit table.blob td.linenumbers a:target {
+	color: goldenrod;
+	text-decoration: underline;
+	outline: none;
+}
+
+div#cgit div#summary {
+	max-width: 80ch;
+}
+
+/* for hilex(1) */
+div#cgit pre .Ke { color: dimgray; }
+div#cgit pre .Ma { color: green; }
+div#cgit pre .Co { color: navy; }
+div#cgit pre .St { color: teal; }
+div#cgit pre .Fo { color: teal; font-weight: bold; }
+div#cgit pre .Su { color: olive; }
+
+/* for htagml(1) */
+div#cgit pre a.tag { color: inherit; text-decoration: underline; }
+div#cgit pre a.tag:target { color: goldenrod; outline: none; }
+
+/* for mandoc(1) */
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
+
+h1.Sh { font-size: 1.5em; }
+table.Nm td:first-child { padding-right: 1ch; }
+code.Fl { white-space: nowrap; }
+span.RsT { font-style: italic; }
+dl.Bl-tag:not(.Bl-compact) dt { margin-top: 1em; }
+ul.Bl-bullet:not(.Bl-compact) li { margin-top: 1em; }
+div.Bd-indent { margin-left: 4ch; }
+table.Bl-column { width: 100%; }
+table.foot { margin-top: 1em; }
+
+div#cgit a.permalink { color: inherit; }
diff --git a/www/git.causal.agency/owner-filter.sh b/www/git.causal.agency/owner-filter.sh
new file mode 100644
index 00000000..18e74cf1
--- /dev/null
+++ b/www/git.causal.agency/owner-filter.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -eu
+
+cat <<EOF
+<a href="https://liberapay.com/june/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
+EOF
diff --git a/www/git.causal.agency/source-filter.sh b/www/git.causal.agency/source-filter.sh
new file mode 100644
index 00000000..514272db
--- /dev/null
+++ b/www/git.causal.agency/source-filter.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+set -eu
+
+ctags=/usr/bin/ctags
+mtags=/usr/local/libexec/mtags
+hilex=/usr/local/libexec/hilex
+htagml=/usr/local/libexec/htagml
+
+case "$1" in
+	(*.[chlmy]|Makefile|*.mk|*.[1-9]|.profile|.shrc|*.sh)
+		tmp=$(mktemp -d -t source-filter)
+		trap 'rm -fr "${tmp}"' EXIT
+		cd "${tmp}"
+		cat >"$1"
+		touch tags
+		case "$1" in
+			(*.[chlmy]) $ctags -w "$1";;
+			(*) $mtags "$1";;
+		esac
+		$hilex -f html "$1" | $htagml -i "$1"
+		;;
+	(*)
+		exec $hilex -t -n "$1" -f html
+		;;
+esac