summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SETUP.md38
-rw-r--r--etc/apache2/conf-available/local-cgit-legacy.conf157
-rw-r--r--etc/apache2/conf-available/local-cgit.conf28
-rw-r--r--etc/cgitrc29
-rw-r--r--etc/uwsgi/apps-available/local-git.ini25
-rwxr-xr-xlib/filters/about-formatting.sh22
-rwxr-xr-xlib/filters/syntax-highlighting.sh36
l---------web/cgit.scss1
-rw-r--r--web/feature.css1
-rw-r--r--web/feature.scss17
10 files changed, 354 insertions, 0 deletions
diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 0000000..9f81afe
--- /dev/null
+++ b/SETUP.md
@@ -0,0 +1,38 @@
+NB! Some system-specific strings are used
+which you may need to adapt depending on your local setup:
+
+featureroot: /usr/local/share/feature/source-hosting
+
+githost: {{githost}}
+
+
+## Prerequisites
+
+Install needed packages:
+
+ sudo apt install git git-daemon-sysvinit cgit cmark highlight
+
+
+## Configure git access
+
+FIXME
+
+
+## Configure web access
+
+Adapt the file `/etc/default/git-daemon`:
+
+ GIT_DAEMON_ENABLE=true
+ GIT_DAEMON_USER=gitdaemon
+ GIT_DAEMON_BASE_PATH=/srv/git
+ GIT_DAEMON_DIRECTORY=/srv/git/{{githost}}
+
+ # Additional options that are passed to the Daemon.
+ GIT_DAEMON_OPTIONS="--interpolated-path=/srv/git/%H/%D --user-path=public_git --export-all"
+
+Adapt the file `/etc/cgitrc`:
+
+ css=/cgit-css/cgit.css
+ logo=/cgit-css/cgit.png
+
+ include=/usr/local/share/feature/source-hosting/source-hosting/etc/cgitrc
diff --git a/etc/apache2/conf-available/local-cgit-legacy.conf b/etc/apache2/conf-available/local-cgit-legacy.conf
new file mode 100644
index 0000000..3a0f593
--- /dev/null
+++ b/etc/apache2/conf-available/local-cgit-legacy.conf
@@ -0,0 +1,157 @@
+# Based on http://www.clearchain.com/blog/posts/cgit-upgrade-gitweb-retired
+#
+# Redirects mapping gitweb -> cgit
+#
+# Gitweb uses get targets seperated by ;
+#
+# /?...;...;...
+#
+# p = Project
+# a = Action like (
+# blob,
+# blob_plain,
+# commitdiff,
+# commit,
+# history,
+# log,
+# rss|atom,
+# shortlog,
+# summary,
+# tag,
+# tree,
+# snapshot
+# )
+# h = SHA Hash
+# hb= SHA Hash Tree Base
+# f= file/dir
+# sf= snapshot format
+#
+# Cgit uses the following:
+#
+# /project/action/?...
+#
+# action ( commit, log, diff, tree, tag, patch )
+#
+# id = SHA Hash
+# id2 = SHA Hash
+# h = head
+#
+# Translation rules
+#
+# Project is a straight redirect
+# ---
+# /?p=(.+)\.git; /$1/
+#
+# Action requires a mapping
+# ---
+# a=(blob|tree) /tree/
+# a=(blobdiff|commitdiff) /diff/
+# a=commit /commit/
+# a=(summary) /
+# a=(shortlog|log|history) /log/
+# a=tag /tag/
+# a=blob_plain /blob/
+# a=rss|atom /atom
+#
+# Targets require mapping
+# ---
+# h=(.+) id=$1
+# f=(.+) /$1
+# hb=(.+) id2=$1
+#
+# Now putting it all together
+#
+RewriteEngine On
+
+# blob
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob;h=([^;]+);hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%5?id=%3;id2=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob;f=([^;]+);h=([^;]+);hb=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%3?id=%4;id2=%5 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob;hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%4?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob;f=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%3 [R=permanent,L,NE,QSD]
+
+# tree
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tree;h=([^;]+);hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%5?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tree;hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/tree/%4?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tree;h=([^;]+);hb=([^;]+)
+RewriteRule ^/$ /%1.git/tree/?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tree;hb=([^;]+)
+RewriteRule ^/$ /%1.git/tree/?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tree
+RewriteRule ^/$ /%1.git/tree/? [R=permanent,L,NE,QSD]
+
+# commitdiff
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blobdiff;h=([^;]+);hp=([^;]+);hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/diff/%6?id2=%4;id=%3;id3=%5 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=commitdiff;h=([^;]+);hp=([^;]+)
+RewriteRule ^/$ /%1.git/diff/?id=%4;id2=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=commitdiff;h=([^;]+)
+RewriteRule ^/$ /%1.git/diff/?id=%3 [R=permanent,L,NE,QSD]
+
+# commit
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=commit;h=([^;]+)
+RewriteRule ^/$ /%1.git/commit/?id=%3 [R=permanent,L,NE,QSD]
+
+# shortlog
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=shortlog;h=([^;]+)
+RewriteRule ^/$ /%1.git/log/?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=shortlog
+RewriteRule ^/$ /%1.git/log/? [R=permanent,L,NE,QSD]
+
+# log
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=log;h=([^;]+)
+RewriteRule ^/$ /%1.git/log/?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=log
+RewriteRule ^/$ /%1.git/log [R=permanent,L,NE,QSD]
+
+# history
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;h=([^;]+);hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/log/%5?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;f=([^;]+);h=([^;]+);hb=([^;]+)
+RewriteRule ^/$ /%1.git/log/%3?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;f=([^;]+);h=([^;]+)
+RewriteRule ^/$ /%1.git/log/%3?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;h=([^;]+);hb=([^;]+)
+RewriteRule ^/$ /%1.git/log/?id=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;hb=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/log/%4?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=history;hb=([^;]+)
+RewriteRule ^/$ /%1.git/log/?id=%3 [R=permanent,L,NE,QSD]
+
+# tag
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=tag;h=([^;]+)
+RewriteRule ^/$ /%1.git/tag/?id=%4 [R=permanent,L,NE,QSD]
+
+# blob_plain
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob_plain;h=([^;]+);f=([^;]+)
+RewriteRule ^/$ /%1.git/blob/%4?id=%3 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob_plain;f=([^;]+);hb=([^;]+)
+RewriteRule ^/$ /%1.git/plain/%3?id2=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=blob_plain;f=([^;]+)
+RewriteRule ^/$ /%1.git/plain/%3 [R=permanent,L,NE,QSD]
+
+# rss|atom
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=(rss|atom);h=refsheads/([^;]+)
+RewriteRule ^/$ /%1.git/atom?h=%4 [R=permanent,L,NE,QSD]
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=(rss|atom)
+RewriteRule ^/$ /%1.git/atom [R=permanent,L,NE,QSD]
+
+# snapshot
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?;a=snapshot;h=([^;]+);sf=([^;]+)
+RewriteRule ^/$ /%1.git/snapshot/%3.tar.gz [R=permanent,L,NE,QSD]
+
+# summary
+RewriteCond %{QUERY_STRING} p=([^;]+)(\.git)(;a=summary)?$
+RewriteRule ^/$ /%1 [R=permanent,L,NE,QSD]
+
+# Failsafes in case nothing above matches,
+# try at least to put the person in the project, else root of cgit
+RewriteCond %{QUERY_STRING} p=([^;]+?)(\.git)?.*
+RewriteRule ^/$ /%1 [R=temp,L,NE,QSD]
+RewriteCond %{QUERY_STRING} .+
+RewriteRule ^/.+ / [R=temp,L,NE,QSD]
diff --git a/etc/apache2/conf-available/local-cgit.conf b/etc/apache2/conf-available/local-cgit.conf
new file mode 100644
index 0000000..f307242
--- /dev/null
+++ b/etc/apache2/conf-available/local-cgit.conf
@@ -0,0 +1,28 @@
+Define FEATUREWEBROOT /usr/local/share/feature/source-hosting/web/
+
+AddDefaultCharset UTF-8
+
+<Directory /usr/share/cgit>
+ Options None
+ AllowOverride None
+ Require all granted
+</Directory>
+
+<Directory ${FEATUREWEBROOT}>
+ Options None
+ AllowOverride None
+ Require all granted
+</Directory>
+
+Alias /cgit-css/ /usr/share/cgit/
+Alias /favicon.ico /usr/share/cgit/favicon.ico
+Alias /robots.txt /usr/share/cgit/robots.txt
+
+Alias /cgit-feature/ ${FEATUREWEBROOT}
+
+# this requires cgit config: enable-index-links=1
+RedirectMatch permanent ^/([^./]+)\.git(/.*) /$1$2
+
+<LocationMatch ^/(?![^./]+\.git/|cgit-css/.*|cgit-local/.*|favicon\.ico|robots\.txt|\.well-known/acme-challenge/)>
+ ProxyPass unix:/run/uwsgi/app/local-git/socket|uwsgi://local-git
+</LocationMatch>
diff --git a/etc/cgitrc b/etc/cgitrc
new file mode 100644
index 0000000..003d7a2
--- /dev/null
+++ b/etc/cgitrc
@@ -0,0 +1,29 @@
+#
+# cgit config
+# see cgitrc(5) for details
+
+# Depends: cmark highlight
+
+css=/cgit-feature/feature.css
+logo=/cgit-css/cgit.png
+
+remove-suffix=1
+
+clone-url=git://$HTTP_HOST/$CGIT_REPO_URL https://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL
+
+# (ab)use source filter to parse markdown as html,
+# with line-numbered + highlighted plaintext only as fallback
+# TODO: add anchor to line-numbering:
+# <li style='list-style-type:none'><a id='n2' href='#n2'>2</a>
+# TODO: hide anchor except in :hover mode:
+# <li><a id='n2' href='#n2'>ยง</a>
+enable-tree-linenumbers=0
+source-filter=/usr/local/share/feature/source-hosting/script/syntax-highlighting.sh
+
+about-filter=/usr/local/share/feature/source-hosting/script/about-formatting.sh
+readme=:README.md
+
+cache-size=1000
+
+# must be last!
+scan-path=$DOCUMENT_ROOT
diff --git a/etc/uwsgi/apps-available/local-git.ini b/etc/uwsgi/apps-available/local-git.ini
new file mode 100644
index 0000000..7fcaf54
--- /dev/null
+++ b/etc/uwsgi/apps-available/local-git.ini
@@ -0,0 +1,25 @@
+[uwsgi]
+
+# serve via uwsgi protocol to frontend webserver
+# * load CGI plugin as default modifier1 to ease frontend setup
+# * load uGreen for cheap concurrency
+plugins = 0:cgi,ugreen
+
+ini = :app
+#ini = :static
+
+[app]
+
+async = 1000
+ugreen = true
+offload-threads = 5
+
+cgi = /=/usr/lib/cgit/cgit.cgi
+
+[static]
+
+# serve static files directly (even better: serve by frontend webserver)
+check-static-docroot = 1
+static-map = /cgit-css/=/usr/share/cgit/
+static-map = /favicon.ico=/usr/share/cgit/favicon.ico
+static-map = /robots.txt=/usr/share/cgit/robots.txt
diff --git a/lib/filters/about-formatting.sh b/lib/filters/about-formatting.sh
new file mode 100755
index 0000000..83a7bc0
--- /dev/null
+++ b/lib/filters/about-formatting.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# This may be used with the about-filter or repo.about-filter setting in cgitrc.
+# It passes formatting of about pages to differing programs, depending on the usage.
+
+# Depends: cmark
+
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL ( = repo.url setting )
+# CGIT_REPO_NAME ( = repo.name setting )
+# CGIT_REPO_PATH ( = repo.path setting )
+# CGIT_REPO_OWNER ( = repo.owner setting )
+# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
+# CGIT_REPO_SECTION ( = section setting )
+# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
+
+case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
+ *.markdown|*.mdown|*.md|*.mkd|*.mdwn) exec cmark --safe; ;;
+ *.htm|*.html) exec cat; ;;
+ *.txt|*) cd /usr/lib/cgit/filters/html-converters; exec ./txt2html; ;;
+esac
diff --git a/lib/filters/syntax-highlighting.sh b/lib/filters/syntax-highlighting.sh
new file mode 100755
index 0000000..44cd310
--- /dev/null
+++ b/lib/filters/syntax-highlighting.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# This script can be used to implement syntax highlighting in the cgit
+# tree-view by referring to this file with the source-filter or repo.source-
+# filter options in cgitrc.
+
+# Depends: cmark highlight
+
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL ( = repo.url setting )
+# CGIT_REPO_NAME ( = repo.name setting )
+# CGIT_REPO_PATH ( = repo.path setting )
+# CGIT_REPO_OWNER ( = repo.owner setting )
+# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
+# CGIT_REPO_SECTION ( = section setting )
+# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
+
+# store filename and extension in local vars
+BASENAME="$1"
+EXTENSION="${BASENAME##*.}"
+
+[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
+[ -z "${EXTENSION}" ] && EXTENSION=txt
+
+# map Makefile and Makefile.* to .mk
+[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
+
+# map less common markdown extensions to .md
+case "$(printf '%s' "$EXTENSION" | tr '[:upper:]' '[:lower:]')" in
+ markdown|mdown|mkd|mdwn) EXTENSION=md;;
+esac
+
+case "$EXTENSION" in
+ md) exec cmark --safe;;
+ *) exec highlight --force -f -I -O xhtml -S "$EXTENSION" --ordered-list --line-number-ref=n 2>/dev/null;;
+esac
diff --git a/web/cgit.scss b/web/cgit.scss
new file mode 120000
index 0000000..995b6b5
--- /dev/null
+++ b/web/cgit.scss
@@ -0,0 +1 @@
+/usr/share/cgit/cgit.css \ No newline at end of file
diff --git a/web/feature.css b/web/feature.css
new file mode 100644
index 0000000..a3f48dc
--- /dev/null
+++ b/web/feature.css
@@ -0,0 +1 @@
+div#cgit{padding:0em;margin:0em;font-family:sans-serif;font-size:10pt;color:#333;background:white;padding:4px}div#cgit a{color:blue;text-decoration:none}div#cgit a:hover{text-decoration:underline}div#cgit table{border-collapse:collapse}div#cgit table#header{width:100%;margin-bottom:1em}div#cgit table#header td.logo{width:96px;vertical-align:top}div#cgit table#header td.main{font-size:250%;padding-left:10px;white-space:nowrap}div#cgit table#header td.main a{color:#000}div#cgit table#header td.form{text-align:right;vertical-align:bottom;padding-right:1em;padding-bottom:2px;white-space:nowrap}div#cgit table#header td.form form,div#cgit table#header td.form input,div#cgit table#header td.form select{font-size:90%}div#cgit table#header td.sub{color:#777;border-top:solid 1px #ccc;padding-left:10px}div#cgit table.tabs{border-bottom:solid 3px #ccc;border-collapse:collapse;margin-top:2em;margin-bottom:0px;width:100%}div#cgit table.tabs td{padding:0px 1em;vertical-align:bottom}div#cgit table.tabs td a{padding:2px 0.75em;color:#777;font-size:110%}div#cgit table.tabs td a.active{color:#000;background-color:#ccc}div#cgit table.tabs a[href^="http://"]:after,div#cgit table.tabs a[href^="https://"]:after{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhcJDQY+gm2TAAAAHWlUWHRDb21tZW50AAAAAABDcmVhdGVkIHdpdGggR0lNUGQuZQcAAABbSURBVAhbY2BABs4MU4CwhYHBh2Erww4wrGFQZHjI8B8IgUIscJWyDHcggltQhI4zGDCcRwhChPggHIggP1QoAVmQkSETrGoHsiAEsACtBYN0oDAMbgU6EBcAAL2eHUt4XUU4AAAAAElFTkSuQmCC);opacity:0.5;margin:0 0 0 5px}div#cgit table.tabs td.form{text-align:right}div#cgit table.tabs td.form form{padding-bottom:2px;font-size:90%;white-space:nowrap}div#cgit table.tabs td.form input,div#cgit table.tabs td.form select{font-size:90%}div#cgit div.path{margin:0px;padding:5px 2em 2px 2em;color:#000;background-color:#eee}div#cgit div.content{margin:0px;padding:2em;border-bottom:solid 3px #ccc}div#cgit table.list{width:100%;border:none;border-collapse:collapse}div#cgit table.list tr{background:white}div#cgit table.list tr.logheader{background:#eee}div#cgit table.list tr:nth-child(even){background:#f7f7f7}div#cgit table.list tr:nth-child(odd){background:white}div#cgit table.list tr:hover{background:#eee}div#cgit table.list tr.nohover{background:white}div#cgit table.list tr.nohover:hover{background:white}div#cgit table.list tr.nohover-highlight:hover:nth-child(even){background:#f7f7f7}div#cgit table.list tr.nohover-highlight:hover:nth-child(odd){background:white}div#cgit table.list th{font-weight:bold;padding:0.1em 0.5em 0.05em 0.5em;vertical-align:baseline}div#cgit table.list td{border:none;padding:0.1em 0.5em 0.1em 0.5em}div#cgit table.list td.commitgraph{font-family:monospace;white-space:pre}div#cgit table.list td.commitgraph .column1{color:#a00}div#cgit table.list td.commitgraph .column2{color:#0a0}div#cgit table.list td.commitgraph .column3{color:#aa0}div#cgit table.list td.commitgraph .column4{color:#00a}div#cgit table.list td.commitgraph .column5{color:#a0a}div#cgit table.list td.commitgraph .column6{color:#0aa}div#cgit table.list td.logsubject{font-family:monospace;font-weight:bold}div#cgit table.list td.logmsg{font-family:monospace;white-space:pre;padding:0 0.5em}div#cgit table.list td a{color:black}div#cgit table.list td a.ls-dir{font-weight:bold;color:#00f}div#cgit table.list td a:hover{color:#00f}div#cgit img{border:none}div#cgit input#switch-btn{margin:2px 0px 0px 0px}div#cgit td#sidebar input.txt{width:100%;margin:2px 0px 0px 0px}div#cgit table#grid{margin:0px}div#cgit td#content{vertical-align:top;padding:1em 2em 1em 1em;border:none}div#cgit div#summary{vertical-align:top;margin-bottom:1em}div#cgit table#downloads{float:right;border-collapse:collapse;border:solid 1px #777;margin-left:0.5em;margin-bottom:0.5em}div#cgit table#downloads th{background-color:#ccc}div#cgit div#blob{border:solid 1px black}div#cgit div.error{color:red;font-weight:bold;margin:1em 2em}div#cgit a.ls-blob,div#cgit a.ls-dir,div#cgit .ls-mod{font-family:monospace}div#cgit td.ls-size{text-align:right;font-family:monospace;width:10em}div#cgit td.ls-mode{font-family:monospace;width:10em}div#cgit table.blob{margin-top:0.5em;border-top:solid 1px black}div#cgit table.blob td.hashes,div#cgit table.blob td.lines{margin:0;padding:0 0 0 0.5em;vertical-align:top;color:black}div#cgit table.blob td.linenumbers{margin:0;padding:0 0.5em 0 0.5em;vertical-align:top;text-align:right;border-right:1px solid gray}div#cgit table.blob pre{padding:0;margin:0}div#cgit table.blob td.linenumbers a,div#cgit table.ssdiff td.lineno a{color:gray;text-align:right;text-decoration:none}div#cgit table.blob td.linenumbers a:hover,div#cgit table.ssdiff td.lineno a:hover{color:black}div#cgit table.blame td.hashes,div#cgit table.blame td.lines,div#cgit table.blame td.linenumbers{padding:0}div#cgit table.blame td.hashes div.alt,div#cgit table.blame td.lines div.alt{padding:0 0.5em 0 0.5em}div#cgit table.blame td.linenumbers div.alt{padding:0 0.5em 0 0}div#cgit table.blame div.alt:nth-child(even){background:#eee}div#cgit table.blame div.alt:nth-child(odd){background:white}div#cgit table.blame td.lines>div{position:relative}div#cgit table.blame td.lines>div>pre{padding:0 0 0 0.5em;position:absolute;top:0}div#cgit table.bin-blob{margin-top:0.5em;border:solid 1px black}div#cgit table.bin-blob th{font-family:monospace;white-space:pre;border:solid 1px #777;padding:0.5em 1em}div#cgit table.bin-blob td{font-family:monospace;white-space:pre;border-left:solid 1px #777;padding:0em 1em}div#cgit table.nowrap td{white-space:nowrap}div#cgit table.commit-info{border-collapse:collapse;margin-top:1.5em}div#cgit div.cgit-panel{float:right;margin-top:1.5em}div#cgit div.cgit-panel table{border-collapse:collapse;border:solid 1px #aaa;background-color:#eee}div#cgit div.cgit-panel th{text-align:center}div#cgit div.cgit-panel td{padding:0.25em 0.5em}div#cgit div.cgit-panel td.label{padding-right:0.5em}div#cgit div.cgit-panel td.ctrl{padding-left:0.5em}div#cgit table.commit-info th{text-align:left;font-weight:normal;padding:0.1em 1em 0.1em 0.1em;vertical-align:top}div#cgit table.commit-info td{font-weight:normal;padding:0.1em 1em 0.1em 0.1em}div#cgit div.commit-subject{font-weight:bold;font-size:125%;margin:1.5em 0em 0.5em 0em;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}div#cgit div.notes{white-space:pre;font-family:monospace;border:solid 1px #ee9;background-color:#ffd;padding:0.3em 2em 0.3em 1em;float:left}div#cgit div.notes-footer{clear:left}div#cgit div.diffstat-header{font-weight:bold;padding-top:1.5em}div#cgit table.diffstat{border-collapse:collapse;border:solid 1px #aaa;background-color:#eee}div#cgit table.diffstat th{font-weight:normal;text-align:left;text-decoration:underline;padding:0.1em 1em 0.1em 0.1em;font-size:100%}div#cgit table.diffstat td{padding:0.2em 0.2em 0.1em 0.1em;font-size:100%;border:none}div#cgit table.diffstat td.mode{white-space:nowrap}div#cgit table.diffstat td span.modechange{padding-left:1em;color:red}div#cgit table.diffstat td.add a{color:green}div#cgit table.diffstat td.del a{color:red}div#cgit table.diffstat td.upd a{color:blue}div#cgit table.diffstat td.graph{width:500px;vertical-align:middle}div#cgit table.diffstat td.graph table{border:none}div#cgit table.diffstat td.graph td{padding:0px;border:0px;height:7pt}div#cgit table.diffstat td.graph td.add{background-color:#5c5}div#cgit table.diffstat td.graph td.rem{background-color:#c55}div#cgit div.diffstat-summary{color:#888;padding-top:0.5em}div#cgit table.diff{width:100%}div#cgit table.diff td{font-family:monospace;white-space:pre}div#cgit table.diff td div.head{font-weight:bold;margin-top:1em;color:black}div#cgit table.diff td div.hunk{color:#009}div#cgit table.diff td div.add{color:green}div#cgit table.diff td div.del{color:red}div#cgit .sha1{font-family:monospace;font-size:90%}div#cgit .left{text-align:left}div#cgit .right{text-align:right}div#cgit table.list td.reposection{font-style:italic;color:#888}div#cgit a.button{font-size:80%;padding:0em 0.5em}div#cgit a.primary{font-size:100%}div#cgit a.secondary{font-size:90%}div#cgit table.list td.sublevel-repo{padding-left:1.5em}div#cgit ul.pager{list-style-type:none;text-align:center;margin:1em 0em 0em 0em;padding:0}div#cgit ul.pager li{display:inline-block;margin:0.25em 0.5em}div#cgit ul.pager a{color:#777}div#cgit ul.pager .current{font-weight:bold}div#cgit span.age-mins{font-weight:bold;color:#080}div#cgit span.age-hours{color:#080}div#cgit span.age-days{color:#040}div#cgit span.age-weeks{color:#444}div#cgit span.age-months{color:#888}div#cgit span.age-years{color:#bbb}div#cgit span.insertions{color:#080}div#cgit span.deletions{color:#800}div#cgit div.footer{margin-top:0.5em;text-align:center;font-size:80%;color:#ccc}div#cgit div.footer a{color:#ccc;text-decoration:none}div#cgit div.footer a:hover{text-decoration:underline}div#cgit a.branch-deco{color:#000;margin:0px 0.5em;padding:0px 0.25em;background-color:#88ff88;border:solid 1px #007700}div#cgit a.tag-deco{color:#000;margin:0px 0.5em;padding:0px 0.25em;background-color:#ffff88;border:solid 1px #777700}div#cgit a.tag-annotated-deco{color:#000;margin:0px 0.5em;padding:0px 0.25em;background-color:#ffcc88;border:solid 1px #777700}div#cgit a.remote-deco{color:#000;margin:0px 0.5em;padding:0px 0.25em;background-color:#ccccff;border:solid 1px #000077}div#cgit a.deco{color:#000;margin:0px 0.5em;padding:0px 0.25em;background-color:#ff8888;border:solid 1px #770000}div#cgit div.commit-subject a.branch-deco,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%}div#cgit table.stats{border:solid 1px black;border-collapse:collapse}div#cgit table.stats th{text-align:left;padding:1px 0.5em;background-color:#eee;border:solid 1px black}div#cgit table.stats td{text-align:right;padding:1px 0.5em;border:solid 1px black}div#cgit table.stats td.total{font-weight:bold;text-align:left}div#cgit table.stats td.sum{color:#c00;font-weight:bold}div#cgit table.stats td.left{text-align:left}div#cgit table.vgraph{border-collapse:separate;border:solid 1px black;height:200px}div#cgit table.vgraph th{background-color:#eee;font-weight:bold;border:solid 1px white;padding:1px 0.5em}div#cgit table.vgraph td{vertical-align:bottom;padding:0px 10px}div#cgit table.vgraph div.bar{background-color:#eee}div#cgit table.hgraph{border:solid 1px black;width:800px}div#cgit table.hgraph th{background-color:#eee;font-weight:bold;border:solid 1px black;padding:1px 0.5em}div#cgit table.hgraph td{vertical-align:middle;padding:2px 2px}div#cgit table.hgraph div.bar{background-color:#eee;height:1em}div#cgit table.ssdiff{width:100%}div#cgit table.ssdiff td{font-size:75%;font-family:monospace;white-space:pre;padding:1px 4px 1px 4px;border-left:solid 1px #aaa;border-right:solid 1px #aaa}div#cgit table.ssdiff td.add{color:black;background:#cfc;min-width:50%}div#cgit table.ssdiff td.add_dark{color:black;background:#aca;min-width:50%}div#cgit table.ssdiff span.add{background:#cfc;font-weight:bold}div#cgit table.ssdiff td.del{color:black;background:#fcc;min-width:50%}div#cgit table.ssdiff td.del_dark{color:black;background:#caa;min-width:50%}div#cgit table.ssdiff span.del{background:#fcc;font-weight:bold}div#cgit table.ssdiff td.changed{color:black;background:#ffc;min-width:50%}div#cgit table.ssdiff td.changed_dark{color:black;background:#cca;min-width:50%}div#cgit table.ssdiff td.lineno{color:black;background:#eee;text-align:right;width:3em;min-width:3em}div#cgit table.ssdiff td.hunk{color:black;background:#ccf;border-top:solid 1px #aaa;border-bottom:solid 1px #aaa}div#cgit table.ssdiff td.head{border-top:solid 1px #aaa;border-bottom:solid 1px #aaa}div#cgit table.ssdiff td.head div.head{font-weight:bold;color:black}div#cgit table.ssdiff td.foot{border-top:solid 1px #aaa;border-left:none;border-right:none;border-bottom:none}div#cgit table.ssdiff td.space{border:none}div#cgit table.ssdiff td.space div{min-height:3em}div#cgit table.blob .num{color:#b07e00}div#cgit table.blob .esc{color:#ff00ff}div#cgit table.blob .str{color:#bf0303}div#cgit table.blob .pps{color:#818100}div#cgit table.blob .slc{color:#838183;font-style:italic}div#cgit table.blob .com{color:#838183;font-style:italic}div#cgit table.blob .ppc{color:#008200}div#cgit table.blob .opt{color:#000000}div#cgit table.blob .lin{color:#555555}div#cgit table.blob .kwa{color:#000000;font-weight:bold}div#cgit table.blob .kwb{color:#0057ae}div#cgit table.blob .kwc{color:#000000;font-weight:bold}div#cgit table.blob .kwd{color:#010181}code>ol.hl{padding:0;margin:0}code>ol.hl>li.hl{height:0}code>ol.hl>li.hl::before{border-left:1px solid grey;margin-right:.5em;content:''}
diff --git a/web/feature.scss b/web/feature.scss
new file mode 100644
index 0000000..061ed55
--- /dev/null
+++ b/web/feature.scss
@@ -0,0 +1,17 @@
+/* usage: sassc --style compressed feature.scss feature.css */
+
+@import 'cgit';
+
+code > ol.hl {
+ padding: 0;
+ margin: 0;
+}
+
+code > ol.hl > li.hl {
+ height: 0;
+ &::before {
+ border-left: 1px solid grey;
+ margin-right: .5em;
+ content: '';
+ }
+}