]> git.armaanb.net Git - stagit.git/commitdiff
Restructure repo
authorArmaan Bhojwani <me@armaanb.net>
Mon, 8 Mar 2021 14:39:49 +0000 (09:39 -0500)
committerArmaan Bhojwani <me@armaanb.net>
Mon, 8 Mar 2021 14:39:49 +0000 (09:39 -0500)
35 files changed:
Makefile
compat.h [deleted file]
contrib/example_create.sh [new file with mode: 0755]
contrib/example_post-receive.sh [new file with mode: 0755]
contrib/repo-gen.sh [new file with mode: 0755]
example_create.sh [deleted file]
example_post-receive.sh [deleted file]
favicon.png [deleted file]
highlight.py [deleted file]
logo.png [deleted file]
man/stagit-index.1 [new file with mode: 0644]
man/stagit.1 [new file with mode: 0644]
reallocarray.c [deleted file]
repo-gen.sh [deleted file]
resources/favicon.png [new file with mode: 0644]
resources/logo.png [new file with mode: 0644]
resources/style.css [new file with mode: 0644]
resources/style.min.css [new file with mode: 0644]
resources/syntax.css [new file with mode: 0644]
src/compat.h [new file with mode: 0644]
src/highlight.py [new file with mode: 0755]
src/reallocarray.c [new file with mode: 0644]
src/stagit-index.c [new file with mode: 0644]
src/stagit.c [new file with mode: 0644]
src/strlcat.c [new file with mode: 0644]
src/strlcpy.c [new file with mode: 0644]
stagit-index.1 [deleted file]
stagit-index.c [deleted file]
stagit.1 [deleted file]
stagit.c [deleted file]
strlcat.c [deleted file]
strlcpy.c [deleted file]
style.css [deleted file]
style.min.css [deleted file]
syntax.css [deleted file]

index c0acb10b336b514285236603f3f6e618b1fc68fe..fbe5c86c5542e5c6482a2c5889dfa3e7a392a642 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -18,27 +18,27 @@ STAGIT_LDFLAGS = ${LIBGIT_LIB} ${LDFLAGS}
 STAGIT_CPPFLAGS = -D_XOPEN_SOURCE=700 -D_DEFAULT_SOURCE -D_BSD_SOURCE
 
 SRC = \
-       stagit.c\
-       stagit-index.c
+       src/stagit.c\
+       src/stagit-index.c
 COMPATSRC = \
-       reallocarray.c\
-       strlcat.c\
-       strlcpy.c
+       src/reallocarray.c\
+       src/strlcat.c\
+       src/strlcpy.c
 BIN = \
        stagit\
        stagit-index
 MAN1 = \
-       stagit.1\
-       stagit-index.1
+       man/stagit.1\
+       man/stagit-index.1
 DOC = \
        LICENSE\
        README
-HDR = compat.h
+HDR = src/compat.h
 
 COMPATOBJ = \
-       reallocarray.o\
-       strlcat.o\
-       strlcpy.o
+       src/reallocarray.o\
+       src/strlcat.o\
+       src/strlcpy.o
 
 OBJ = ${SRC:.c=.o} ${COMPATOBJ}
 
@@ -54,8 +54,7 @@ dist:
        rm -rf ${NAME}-${VERSION}
        mkdir -p ${NAME}-${VERSION}
        cp -f ${MAN1} ${HDR} ${SRC} ${COMPATSRC} ${DOC} \
-               Makefile favicon.png logo.png style.min.css syntax.css highlight.py \
-               example_create.sh example_post-receive.sh \
+               Makefile resources/* src/highlight.py contrib/* \
                ${NAME}-${VERSION}
        # make tarball
        tar -cf - ${NAME}-${VERSION} | \
@@ -64,12 +63,12 @@ dist:
 
 ${OBJ}: ${HDR}
 
-stagit: stagit.o ${COMPATOBJ} requirements.txt
+stagit: src/stagit.o ${COMPATOBJ} requirements.txt
        pip3 install -r requirements.txt
-       ${CC} -o $@ stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
+       ${CC} -o $@ src/stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
 
-stagit-index: stagit-index.o ${COMPATOBJ}
-       ${CC} -o $@ stagit-index.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
+stagit-index: src/stagit-index.o ${COMPATOBJ}
+       ${CC} -o $@ src/stagit-index.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
 
 clean:
        rm -f ${BIN} ${OBJ} ${NAME}-${VERSION}.tar.gz
@@ -81,36 +80,27 @@ install: all
        for f in ${BIN}; do chmod 755 ${DESTDIR}${PREFIX}/bin/$$f; done
        # installing example files.
        mkdir -p ${DESTDIR}${DOCPREFIX}
-       cp -f favicon.png\
-               logo.png\
-               example_create.sh\
-               example_post-receive.sh\
+       cp -f resources/*.png\
+               contrib/*\
                README\
                ${DESTDIR}${DOCPREFIX}
        mkdir -p ${DESTDIR}${SHAREPREFIX}
-       cp -f style.min.css\
-               syntax.css\
-               highlight.py\
+       cp -f resources/*\
+               src/highlight.py\
                ${DESTDIR}${SHAREPREFIX}
        # installing manual pages.
        mkdir -p ${DESTDIR}${MANPREFIX}/man1
        cp -f ${MAN1} ${DESTDIR}${MANPREFIX}/man1
-       for m in ${MAN1}; do chmod 644 ${DESTDIR}${MANPREFIX}/man1/$$m; done
+       install -Dm644 man/* ${DESTDIR}${MANPREFIX}/man1
 
 uninstall:
        # removing executable files.
        for f in ${BIN}; do rm -f ${DESTDIR}${PREFIX}/bin/$$f; done
        # removing example files.
-       rm -f \
-               ${DESTDIR}${SHAREPREFIX}/style.min.css\
-               ${DESTDIR}${SHAREPREFIX}/syntax.css\
-               ${DESTDIR}${SHAREPREFIX}/highlight.py\
-               ${DESTDIR}${DOCPREFIX}/favicon.png\
-               ${DESTDIR}${DOCPREFIX}/logo.png\
-               ${DESTDIR}${DOCPREFIX}/example_create.sh\
-               ${DESTDIR}${DOCPREFIX}/example_post-receive.sh\
-               ${DESTDIR}${DOCPREFIX}/README
-       -rmdir ${DESTDIR}${DOCPREFIX}
+       rm -f ${DESTDIR}${SHAREPREFIX}/*
+       rm -f ${DESTDIR}${DOCPREFIX}/*
+       rmdir ${DESTDIR}${SHAREPREFIX}
+       rmdir ${DESTDIR}${DOCPREFIX}
        # removing manual pages.
        for m in ${MAN1}; do rm -f ${DESTDIR}${MANPREFIX}/man1/$$m; done
 
diff --git a/compat.h b/compat.h
deleted file mode 100644 (file)
index f97a69b..0000000
--- a/compat.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#undef strlcat
-size_t strlcat(char *, const char *, size_t);
-#undef strlcpy
-size_t strlcpy(char *, const char *, size_t);
-#undef reallocarray
-void *reallocarray(void *, size_t, size_t);
diff --git a/contrib/example_create.sh b/contrib/example_create.sh
new file mode 100755 (executable)
index 0000000..cf16e1d
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# - Makes index for repositories in a single directory.
+# - Makes static pages for each repository directory.
+#
+# NOTE, things to do manually (once) before running this script:
+# - copy style.css, logo.png and favicon.png manually, a style.css example
+#   is included.
+#
+# - write clone url, for example "git://git.codemadness.org/dir" to the "url"
+#   file for each repo.
+# - write owner of repo to the "owner" file.
+# - write description in "description" file.
+#
+# Usage:
+# - mkdir -p htmldir && cd htmldir
+# - sh example_create.sh
+
+# path must be absolute.
+reposdir="/var/www/domains/git.codemadness.nl/home/src"
+curdir="$(pwd)"
+
+# make index.
+stagit-index "${reposdir}/"*/ > "${curdir}/index.html"
+
+# make files per repo.
+for dir in "${reposdir}/"*/; do
+       # strip .git suffix.
+       r=$(basename "${dir}")
+       d=$(basename "${dir}" ".git")
+       printf "%s... " "${d}"
+
+       mkdir -p "${curdir}/${d}"
+       cd "${curdir}/${d}" || continue
+       stagit -c ".cache" "${reposdir}/${r}"
+
+       # symlinks
+       ln -sf log.html index.html
+       ln -sf ../style.css style.css
+       ln -sf ../logo.png logo.png
+       ln -sf ../favicon.png favicon.png
+
+       echo "done"
+done
diff --git a/contrib/example_post-receive.sh b/contrib/example_post-receive.sh
new file mode 100755 (executable)
index 0000000..c9bcb5e
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+# generic git post-receive hook.
+# change the config options below and call this script in your post-receive
+# hook or symlink it.
+#
+# usage: $0 [name]
+#
+# if name is not set the basename of the current directory is used,
+# this is the directory of the repo when called from the post-receive script.
+
+# NOTE: needs to be set for correct locale (expects UTF-8) otherwise the
+#       default is LC_CTYPE="POSIX".
+export LC_CTYPE="en_US.UTF-8"
+
+name="$1"
+if test "${name}" = ""; then
+       name=$(basename "$(pwd)")
+fi
+
+# config
+# paths must be absolute.
+reposdir="/home/src/src"
+dir="${reposdir}/${name}"
+htmldir="/home/www/domains/git.codemadness.org/htdocs"
+stagitdir="/"
+destdir="${htmldir}${stagitdir}"
+cachefile=".htmlcache"
+# /config
+
+if ! test -d "${dir}"; then
+       echo "${dir} does not exist" >&2
+       exit 1
+fi
+cd "${dir}" || exit 1
+
+# detect git push -f
+force=0
+while read -r old new ref; do
+       test "${old}" = "0000000000000000000000000000000000000000" && continue
+       test "${new}" = "0000000000000000000000000000000000000000" && continue
+
+       hasrevs=$(git rev-list "${old}" "^${new}" | sed 1q)
+       if test -n "${hasrevs}"; then
+               force=1
+               break
+       fi
+done
+
+# strip .git suffix.
+r=$(basename "${name}")
+d=$(basename "${name}" ".git")
+printf "[%s] stagit HTML pages... " "${d}"
+
+mkdir -p "${destdir}/${d}"
+cd "${destdir}/${d}" || exit 1
+
+# remove commits and ${cachefile} on git push -f, this recreated later on.
+if test "${force}" = "1"; then
+       rm -f "${cachefile}"
+       rm -rf "commit"
+fi
+
+# make index.
+stagit-index "${reposdir}/"*/ > "${destdir}/index.html"
+
+# make pages.
+stagit -c "${cachefile}" "${reposdir}/${r}"
+
+ln -sf log.html index.html
+ln -sf ../style.css style.css
+ln -sf ../logo.png logo.png
+
+echo "done"
diff --git a/contrib/repo-gen.sh b/contrib/repo-gen.sh
new file mode 100755 (executable)
index 0000000..1a33ee9
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+fresh=false
+unique=false
+
+for arg in "$@"; do
+       [ "$unique" = true ]   && unique="$arg"
+       [ "$arg" = "--only" ]  && unique=true
+       [ "$arg" = "--fresh" ] && fresh=true
+done
+
+[ "$unique" = true ] && {
+       echo "Expected argument after \`--only\`.";
+       exit 1;
+}
+
+STAGIT=/var/www/git/stagit.out
+[ ! -f "$STAGIT" ] && STAGIT=stagit
+
+STAGIT_INDEX=/var/www/git/stagit-index.out
+[ ! -f "$STAGIT_INDEX" ] && STAGIT_INDEX=stagit-index
+
+build_html () {
+       repo="$1"
+       repo="$(basename "$repo" | sed 's/\.git$//g')"
+
+       [ "$fresh" = true ] && {
+               echo "Deleting HTML for $repo.";
+               rm -fr "/var/www/git/$repo";
+       }
+       mkdir -p "/var/www/git/$repo"
+       cd "/var/www/git/$repo" || { echo "Couldn't cd."; exit 1; }
+
+       [ ! -f style.css ]   && ln -s ../style.css ./
+       [ ! -f favicon.png ] && ln -s ../favicon.png ./
+       [ ! -f logo.png ]    && ln -s ../logo.png ./
+       [ ! -f highlight ]   && ln -s ../highlight ./
+       [ ! -f index.html ]  && ln -s log.html index.html
+
+       echo "git://git.knutsen.co/$repo" > "/srv/git/$repo.git/url"
+
+       COMMAND="$STAGIT -c ./cachefile /srv/git/$repo.git"
+       echo "Building web-page for $repo."
+       echo "$COMMAND"
+       $COMMAND
+}
+
+if [ "$unique" = false ]; then
+       for repo in /srv/git/*.git; do
+               build_html "$repo"
+       done
+else
+       build_html "$unique"
+fi
+
+echo "Generating index.html with \`$STAGIT_INDEX\`."
+"$STAGIT_INDEX" /srv/git/*.git > /var/www/git/index.html
+
+# Correct ownership of the web files.
+chown git:www-data -R /var/www/git -f
+chmod         g+rw -R /var/www/git -f
diff --git a/example_create.sh b/example_create.sh
deleted file mode 100755 (executable)
index cf16e1d..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/sh
-# - Makes index for repositories in a single directory.
-# - Makes static pages for each repository directory.
-#
-# NOTE, things to do manually (once) before running this script:
-# - copy style.css, logo.png and favicon.png manually, a style.css example
-#   is included.
-#
-# - write clone url, for example "git://git.codemadness.org/dir" to the "url"
-#   file for each repo.
-# - write owner of repo to the "owner" file.
-# - write description in "description" file.
-#
-# Usage:
-# - mkdir -p htmldir && cd htmldir
-# - sh example_create.sh
-
-# path must be absolute.
-reposdir="/var/www/domains/git.codemadness.nl/home/src"
-curdir="$(pwd)"
-
-# make index.
-stagit-index "${reposdir}/"*/ > "${curdir}/index.html"
-
-# make files per repo.
-for dir in "${reposdir}/"*/; do
-       # strip .git suffix.
-       r=$(basename "${dir}")
-       d=$(basename "${dir}" ".git")
-       printf "%s... " "${d}"
-
-       mkdir -p "${curdir}/${d}"
-       cd "${curdir}/${d}" || continue
-       stagit -c ".cache" "${reposdir}/${r}"
-
-       # symlinks
-       ln -sf log.html index.html
-       ln -sf ../style.css style.css
-       ln -sf ../logo.png logo.png
-       ln -sf ../favicon.png favicon.png
-
-       echo "done"
-done
diff --git a/example_post-receive.sh b/example_post-receive.sh
deleted file mode 100755 (executable)
index c9bcb5e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/sh
-# generic git post-receive hook.
-# change the config options below and call this script in your post-receive
-# hook or symlink it.
-#
-# usage: $0 [name]
-#
-# if name is not set the basename of the current directory is used,
-# this is the directory of the repo when called from the post-receive script.
-
-# NOTE: needs to be set for correct locale (expects UTF-8) otherwise the
-#       default is LC_CTYPE="POSIX".
-export LC_CTYPE="en_US.UTF-8"
-
-name="$1"
-if test "${name}" = ""; then
-       name=$(basename "$(pwd)")
-fi
-
-# config
-# paths must be absolute.
-reposdir="/home/src/src"
-dir="${reposdir}/${name}"
-htmldir="/home/www/domains/git.codemadness.org/htdocs"
-stagitdir="/"
-destdir="${htmldir}${stagitdir}"
-cachefile=".htmlcache"
-# /config
-
-if ! test -d "${dir}"; then
-       echo "${dir} does not exist" >&2
-       exit 1
-fi
-cd "${dir}" || exit 1
-
-# detect git push -f
-force=0
-while read -r old new ref; do
-       test "${old}" = "0000000000000000000000000000000000000000" && continue
-       test "${new}" = "0000000000000000000000000000000000000000" && continue
-
-       hasrevs=$(git rev-list "${old}" "^${new}" | sed 1q)
-       if test -n "${hasrevs}"; then
-               force=1
-               break
-       fi
-done
-
-# strip .git suffix.
-r=$(basename "${name}")
-d=$(basename "${name}" ".git")
-printf "[%s] stagit HTML pages... " "${d}"
-
-mkdir -p "${destdir}/${d}"
-cd "${destdir}/${d}" || exit 1
-
-# remove commits and ${cachefile} on git push -f, this recreated later on.
-if test "${force}" = "1"; then
-       rm -f "${cachefile}"
-       rm -rf "commit"
-fi
-
-# make index.
-stagit-index "${reposdir}/"*/ > "${destdir}/index.html"
-
-# make pages.
-stagit -c "${cachefile}" "${reposdir}/${r}"
-
-ln -sf log.html index.html
-ln -sf ../style.css style.css
-ln -sf ../logo.png logo.png
-
-echo "done"
diff --git a/favicon.png b/favicon.png
deleted file mode 100644 (file)
index b2e91c9..0000000
Binary files a/favicon.png and /dev/null differ
diff --git a/highlight.py b/highlight.py
deleted file mode 100755 (executable)
index 2b81d8c..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python3
-
-import pygments
-from pygments import highlight
-from pygments.formatters import HtmlFormatter
-import pygments.lexers
-from markdown import markdown
-
-from sys import stdin, stderr
-
-filename = stdin.readline().strip()
-contents = stdin.read()
-lexer = None
-
-try:
-    lexer = pygments.lexers.guess_lexer_for_filename(filename, contents)
-except pygments.util.ClassNotFound:
-    try:
-        lexer = pygments.lexers.guess_lexer(contents)
-    except pygments.util.ClassNotFound:
-        pass
-
-if lexer is None:
-    lexer = pygments.lexers.special.TextLexer
-
-rendered = (
-    markdown(
-        contents,
-        extensions=[
-            "codehilite",
-            "extra",
-            "sane_lists",
-            "smarty",
-            "pymdownx.tasklist",
-        ],
-    )
-    if lexer.__class__ is pygments.lexers.MarkdownLexer
-    else None
-)
-
-formatter = HtmlFormatter(
-    style="monokai",
-    cssclass="highlight",
-    linenos="table",
-    lineanchors="loc",
-    anchorlinenos=True,
-)
-
-outp = ""
-if rendered:
-    outp += '<article class="markup markdown">'
-    outp += rendered
-    outp += "</article>"
-outp += f'<div id="blob">{highlight(contents, lexer, formatter)}</div>"'
-outp += '<link rel="stylesheet" href="/syntax.css"'
-
-print(outp)
-
-print(f"Filename: {filename}; Lexer: {lexer}.", file=stderr)
-
-if rendered:
-    print("Markdown was rendered in addition.", file=stderr)
diff --git a/logo.png b/logo.png
deleted file mode 100644 (file)
index b2e91c9..0000000
Binary files a/logo.png and /dev/null differ
diff --git a/man/stagit-index.1 b/man/stagit-index.1
new file mode 100644 (file)
index 0000000..9abd448
--- /dev/null
@@ -0,0 +1,43 @@
+.Dd March 2021
+.Dt STAGIT-INDEX 1
+.Os
+.Sh NAME
+.Nm stagit-index
+.Nd static git index page generator
+.Sh SYNOPSIS
+.Nm
+.Op Ar repodir...
+.Sh DESCRIPTION
+.Nm
+will create an index HTML page for the repositories specified and writes
+the HTML data to stdout.
+The repos in the index are in the same order as the arguments
+.Ar repodir
+specified.
+.Pp
+The basename of the directory is used as the repository name.
+The suffix ".git" is removed from the basename, this suffix is commonly used
+for "bare" repos.
+.Pp
+The content of the follow files specifies the meta data for each repository:
+.Bl -tag -width Ds
+.It .git/description or description (bare repos).
+description
+.It .git/owner or owner (bare repo).
+owner of repository
+.El
+.Pp
+For changing the style of the page you can use the following files:
+.Bl -tag -width Ds
+.It favicon.png
+favicon image.
+.It logo.png
+32x32 logo.
+.It style.css
+CSS stylesheet.
+.El
+.Sh SEE ALSO
+.Xr stagit 1
+.Sh AUTHORS
+.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
+.An Armaan Bhojwani Aq Mt me@armaanb.net
diff --git a/man/stagit.1 b/man/stagit.1
new file mode 100644 (file)
index 0000000..23e7ca7
--- /dev/null
@@ -0,0 +1,108 @@
+.Dd March 2021
+.Dt STAGIT 1
+.Os
+.Sh NAME
+.Nm stagit
+.Nd static git page generator
+.Sh SYNOPSIS
+.Nm
+.Op Fl c Ar cachefile
+.Op Fl l Ar commits
+.Ar repodir
+.Sh DESCRIPTION
+.Nm
+writes HTML pages for the repository
+.Ar repodir
+to the current directory.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar cachefile
+Cache the entries of the log page up to the point of
+the last commit.
+The
+.Ar cachefile
+will store the last commit id and the entries in the HTML table.
+It is up to the user to make sure the state of the
+.Ar cachefile
+is in sync with the history of the repository.
+.It Fl l Ar commits
+Write a maximum number of
+.Ar commits
+to the log.html file only.
+However the commit files are written as usual.
+.El
+.Pp
+The options
+.Fl c
+and
+.Fl l
+cannot be used at the same time.
+.Pp
+The following files will be written:
+.Bl -tag -width Ds
+.It atom.xml
+Atom XML feed
+.It files.html
+List of files in the latest tree, linking to the file.
+.It log.html
+List of commits in reverse chronological applied commit order, each commit
+links to a page with a diffstat and diff of the commit.
+.It refs.html
+Lists references of the repository such as branches and tags.
+.El
+.Pp
+For each entry in HEAD a file will be written in the format:
+file/filepath.html.
+This file will contain the textual data of the file prefixed by line numbers.
+The file will have the string "Binary file" if the data is considered to be
+non-textual.
+.Pp
+For each commit a file will be written in the format:
+commit/commitid.html.
+This file will contain the diffstat and diff of the commit.
+It will write the string "Binary files differ" if the data is considered to
+be non-textual.
+Too large diffs will be suppressed and a string
+"Diff is too large, output suppressed" will be written.
+.Pp
+When a commit HTML file exists it won't be overwritten again, note that if
+you've changed
+.Nm
+or changed one of the metadata files of the repository it is recommended to
+recreate all the output files because it will contain old data.
+To do this remove the output directory and
+.Ar cachefile ,
+then recreate the files.
+.Pp
+The basename of the directory is used as the repository name.
+The suffix ".git" is removed from the basename, this suffix is commonly used
+for "bare" repos.
+.Pp
+The content of the follow files specifies the metadata for each repository:
+.Bl -tag -width Ds
+.It .git/description or description (bare repo).
+description
+.It .git/owner or owner (bare repo).
+owner of repository
+.It .git/url or url (bare repo).
+primary clone url of the repository, for example: git://git.2f30.org/stagit
+.El
+.Pp
+When a README or LICENSE file exists in HEAD or a .gitmodules submodules file
+exists in HEAD a direct link in the menu is made.
+.Pp
+For changing the style of the page you can use the following files:
+.Bl -tag -width Ds
+.It favicon.png
+favicon image.
+.It logo.png
+32x32 logo.
+.It style.css
+CSS stylesheet.
+.El
+.Sh SEE ALSO
+.Xr stagit-index 1
+.Sh AUTHORS
+.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
+.An Armaan Bhojwani Aq Mt me@armaanb.net
diff --git a/reallocarray.c b/reallocarray.c
deleted file mode 100644 (file)
index b92dae5..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <sys/types.h>
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "compat.h"
-
-/*
- * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
- * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
- */
-#define MUL_NO_OVERFLOW        (1UL << (sizeof(size_t) * 4))
-
-void *
-reallocarray(void *optr, size_t nmemb, size_t size)
-{
-       if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
-           nmemb > 0 && SIZE_MAX / nmemb < size) {
-               errno = ENOMEM;
-               return NULL;
-       }
-       return realloc(optr, size * nmemb);
-}
diff --git a/repo-gen.sh b/repo-gen.sh
deleted file mode 100755 (executable)
index 1a33ee9..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-
-fresh=false
-unique=false
-
-for arg in "$@"; do
-       [ "$unique" = true ]   && unique="$arg"
-       [ "$arg" = "--only" ]  && unique=true
-       [ "$arg" = "--fresh" ] && fresh=true
-done
-
-[ "$unique" = true ] && {
-       echo "Expected argument after \`--only\`.";
-       exit 1;
-}
-
-STAGIT=/var/www/git/stagit.out
-[ ! -f "$STAGIT" ] && STAGIT=stagit
-
-STAGIT_INDEX=/var/www/git/stagit-index.out
-[ ! -f "$STAGIT_INDEX" ] && STAGIT_INDEX=stagit-index
-
-build_html () {
-       repo="$1"
-       repo="$(basename "$repo" | sed 's/\.git$//g')"
-
-       [ "$fresh" = true ] && {
-               echo "Deleting HTML for $repo.";
-               rm -fr "/var/www/git/$repo";
-       }
-       mkdir -p "/var/www/git/$repo"
-       cd "/var/www/git/$repo" || { echo "Couldn't cd."; exit 1; }
-
-       [ ! -f style.css ]   && ln -s ../style.css ./
-       [ ! -f favicon.png ] && ln -s ../favicon.png ./
-       [ ! -f logo.png ]    && ln -s ../logo.png ./
-       [ ! -f highlight ]   && ln -s ../highlight ./
-       [ ! -f index.html ]  && ln -s log.html index.html
-
-       echo "git://git.knutsen.co/$repo" > "/srv/git/$repo.git/url"
-
-       COMMAND="$STAGIT -c ./cachefile /srv/git/$repo.git"
-       echo "Building web-page for $repo."
-       echo "$COMMAND"
-       $COMMAND
-}
-
-if [ "$unique" = false ]; then
-       for repo in /srv/git/*.git; do
-               build_html "$repo"
-       done
-else
-       build_html "$unique"
-fi
-
-echo "Generating index.html with \`$STAGIT_INDEX\`."
-"$STAGIT_INDEX" /srv/git/*.git > /var/www/git/index.html
-
-# Correct ownership of the web files.
-chown git:www-data -R /var/www/git -f
-chmod         g+rw -R /var/www/git -f
diff --git a/resources/favicon.png b/resources/favicon.png
new file mode 100644 (file)
index 0000000..b2e91c9
Binary files /dev/null and b/resources/favicon.png differ
diff --git a/resources/logo.png b/resources/logo.png
new file mode 100644 (file)
index 0000000..b2e91c9
Binary files /dev/null and b/resources/logo.png differ
diff --git a/resources/style.css b/resources/style.css
new file mode 100644 (file)
index 0000000..af0d598
--- /dev/null
@@ -0,0 +1,211 @@
+body {
+       font-size: 0.8rem;
+       font-family: monospace;
+       color: #ebdbb2;
+       background-color: #282828;
+       max-width: max-content;
+       min-width: min-content;
+}
+
+pre {
+       tab-size: 4;
+}
+
+h1, h2, h3, h4, h5, h6 {
+       margin: 0;
+}
+
+img, h1, h2 {
+       vertical-align: middle;
+}
+
+img {
+       border: 0;
+}
+
+a:target {
+       background-color: #eee;
+}
+
+a.d,
+a.h,
+a.i,
+a.line {
+       text-decoration: none;
+}
+
+#blob {
+       display: block;
+       max-width: 100%;
+}
+
+article.markup {
+       font-size: 15px;
+       border: 2px solid #eee;
+       border-radius: 10px;
+       font-family: sans-serif;
+       padding: 2.5em;
+       margin: 2em 0;
+}
+
+article.markup code {
+       font-size: 0.9em;
+       border: 1px solid #dbdbdb;
+       background-color: #f7f7f7;
+       padding: 0 0.3em;
+       border-radius: 0.3em
+}
+
+article.markup pre code {
+       border: none;
+       background: none;
+       padding: 0;
+       border-radius: 0;
+}
+
+article.markup pre {
+       background-color: #f7f7f7;
+       padding: 1em;
+       border: 1px solid #dbdbdb;
+       border-radius: 0.3em;
+}
+
+article.markup h1 {
+       font-size: 2.4em;
+       padding-bottom: 6px;
+       border-bottom: 5px solid #0000000a;
+}
+
+article.markup h2 {
+       font-size: 1.9em;
+       padding-bottom: 5px;
+       border-bottom: 2px solid #00000014;
+}
+
+article.markup h3 {
+       font-size: 1.5em;
+}
+
+article.markup h4 {
+       font-size: 1.3em;
+}
+
+article.markup h5 {
+       font-size: 1.1em;
+}
+
+article.markup h6 {
+       font-size: 1em;
+}
+
+article img {
+       max-width: 100%;
+}
+
+.linenos {
+       margin-right: 0;
+       border-right: 1px solid rgb(0 0 0 / 8%);
+       user-select: none;
+}
+
+.linenos a {
+       margin-right: 0.9em;
+       user-select: none;
+       text-decoration: none;
+}
+
+#blob a {
+       color: #777;
+}
+
+#blob a:hover {
+       color: #eee;
+       text-decoration: none;
+}
+
+a {
+       color: #83a598;
+}
+
+table thead td {
+       font-weight: bold;
+}
+
+table td {
+       padding: 0 0.4em;
+}
+
+#content table td {
+       vertical-align: top;
+       white-space: nowrap;
+}
+
+#branches tr:hover td,
+#tags tr:hover td,
+#index tr:hover td,
+#log tr:hover td,
+#files tr:hover td {
+       background-color: #333;
+}
+
+#index tr td:nth-child(2),
+#tags tr td:nth-child(3),
+#branches tr td:nth-child(3),
+#log tr td:nth-child(2) {
+       white-space: normal;
+}
+
+td.num {
+       text-align: right;
+}
+
+.desc {
+       color: #928374;
+}
+
+hr {
+       border: 0;
+       border-top: 1px solid #928374;
+       height: 1px;
+}
+
+pre {
+       font-family: monospace;
+}
+
+pre a.h {
+       color: #fe8019;
+}
+
+.A,
+span.i,
+pre a.i {
+       color: #b8bb26;
+}
+
+.D,
+span.d,
+pre a.d {
+       color: #fb4934;
+}
+
+pre a.h:hover,
+pre a.i:hover,
+pre a.d:hover {
+       text-decoration: none;
+}
+
+.url td:nth-child(2) {
+       padding-top:    0.2em;
+       padding-bottom: 0.9em;
+}
+
+.url td:nth-child(2) span {
+       padding: 1px 5px;
+       border: 1px solid #ebdbb2;
+       border-radius: 5px;
+}
+
+.url td:nth-child(2) span a {
+       color: #ebdbb2;
+}
diff --git a/resources/style.min.css b/resources/style.min.css
new file mode 100644 (file)
index 0000000..063054a
--- /dev/null
@@ -0,0 +1 @@
+body{font-size:.8rem;font-family:monospace;color:#ebdbb2;background-color:#282828;max-width:max-content;min-width:min-content}pre{tab-size:4}h1,h2,h3,h4,h5,h6{margin:0}img,h1,h2{vertical-align:middle}img{border:0}a:target{background-color:#eee}a.d,a.h,a.i,a.line{text-decoration:none}#blob{display:block;max-width:100%}article.markup{font-size:15px;border:2px solid #eee;border-radius:10px;font-family:sans-serif;padding:2.5em;margin:2em 0}article.markup code{font-size:.9em;border:1px solid #dbdbdb;background-color:#f7f7f7;padding:0 .3em;border-radius:.3em}article.markup pre code{border:none;background:0 0;padding:0;border-radius:0}article.markup pre{background-color:#f7f7f7;padding:1em;border:1px solid #dbdbdb;border-radius:.3em}article.markup h1{font-size:2.4em;padding-bottom:6px;border-bottom:5px solid #0000000a}article.markup h2{font-size:1.9em;padding-bottom:5px;border-bottom:2px solid #00000014}article.markup h3{font-size:1.5em}article.markup h4{font-size:1.3em}article.markup h5{font-size:1.1em}article.markup h6{font-size:1em}article img{max-width:100%}.linenos{margin-right:0;border-right:1px solid rgb(0 0 0/8%);user-select:none}.linenos a{margin-right:.9em;user-select:none;text-decoration:none}#blob a{color:#777}#blob a:hover{color:#eee;text-decoration:none}a{color:#83a598}table thead td{font-weight:700}table td{padding:0 .4em}#content table td{vertical-align:top;white-space:nowrap}#branches tr:hover td,#tags tr:hover td,#index tr:hover td,#log tr:hover td,#files tr:hover td{background-color:#333}#index tr td:nth-child(2),#tags tr td:nth-child(3),#branches tr td:nth-child(3),#log tr td:nth-child(2){white-space:normal}td.num{text-align:right}.desc{color:#928374}hr{border:0;border-top:1px solid #928374;height:1px}pre{font-family:monospace}pre a.h{color:#fe8019}.A,span.i,pre a.i{color:#b8bb26}.D,span.d,pre a.d{color:#fb4934}pre a.h:hover,pre a.i:hover,pre a.d:hover{text-decoration:none}.url td:nth-child(2){padding-top:.2em;padding-bottom:.9em}.url td:nth-child(2) span{padding:1px 5px;border:1px solid #ebdbb2;border-radius:5px}.url td:nth-child(2) span a{color:#ebdbb2}
\ No newline at end of file
diff --git a/resources/syntax.css b/resources/syntax.css
new file mode 100644 (file)
index 0000000..4c774f6
--- /dev/null
@@ -0,0 +1 @@
+.highlight .hll{background-color:#ffc}.highlight{background:#282828;color:#ebdbb2;background-color:#282828}.highlight .c{color:#928374;font-style:italic;background-color:#282828}.highlight .err{color:#ebdbb2;background-color:#282828}.highlight .esc{color:#ebdbb2;background-color:#282828}.highlight .g{color:#ebdbb2;background-color:#282828}.highlight .k{color:#fe8019;background-color:#282828}.highlight .l{color:#ebdbb2;background-color:#282828}.highlight .n{color:#ebdbb2;background-color:#282828}.highlight .o{color:#fe8019;background-color:#282828}.highlight .x{color:#ebdbb2;background-color:#282828}.highlight .p{color:#ebdbb2;background-color:#282828}.highlight .ch{color:#928374;font-style:italic;background-color:#282828}.highlight .cm{color:#928374;font-style:italic;background-color:#282828}.highlight .cp{color:#8ec07c;background-color:#282828}.highlight .c1{color:#928374;font-style:italic;background-color:#282828}.highlight .cs{color:#928374;font-style:italic;background-color:#282828}.highlight .gd{color:#282828;background-color:#fb4934}.highlight .ge{color:#83a598;text-decoration:underline;background-color:#282828}.highlight .gr{color:#ebdbb2;font-weight:700;background-color:#fb4934}.highlight .gh{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .gi{color:#282828;background-color:#b8bb26}.highlight .go{color:#504945;background-color:#282828}.highlight .gp{color:#ebdbb2;background-color:#282828}.highlight .gs{color:#ebdbb2;background-color:#282828}.highlight .gu{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .gt{color:#ebdbb2;font-weight:700;background-color:#fb4934}.highlight .kc{color:#fe8019;background-color:#282828}.highlight .kd{color:#fe8019;background-color:#282828}.highlight .kn{color:#fe8019;background-color:#282828}.highlight .kp{color:#fe8019;background-color:#282828}.highlight .kr{color:#fe8019;background-color:#282828}.highlight .kt{color:#fabd2f;background-color:#282828}.highlight .ld{color:#ebdbb2;background-color:#282828}.highlight .m{color:#d3869b;background-color:#282828}.highlight .s{color:#b8bb26;background-color:#282828}.highlight .na{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .nb{color:#fabd2f;background-color:#282828}.highlight .nc{color:#ebdbb2;background-color:#282828}.highlight .no{color:#d3869b;background-color:#282828}.highlight .nd{color:#ebdbb2;background-color:#282828}.highlight .ni{color:#fabd2f;background-color:#282828}.highlight .ne{color:#fb4934;background-color:#282828}.highlight .nf{color:#fabd2f;background-color:#282828}.highlight .nl{color:#fb4934;background-color:#282828}.highlight .nn{color:#ebdbb2;background-color:#282828}.highlight .nx{color:#ebdbb2;background-color:#282828}.highlight .py{color:#ebdbb2;background-color:#282828}.highlight .nt{color:#fb4934;background-color:#282828}.highlight .nv{color:#ebdbb2;background-color:#282828}.highlight .ow{color:#fe8019;background-color:#282828}.highlight .w{color:#ebdbb2;background-color:#282828}.highlight .mb{color:#d3869b;background-color:#282828}.highlight .mf{color:#d3869b;background-color:#282828}.highlight .mh{color:#d3869b;background-color:#282828}.highlight .mi{color:#d3869b;background-color:#282828}.highlight .mo{color:#d3869b;background-color:#282828}.highlight .sb{color:#b8bb26;background-color:#282828}.highlight .sc{color:#b8bb26;background-color:#282828}.highlight .sd{color:#b8bb26;background-color:#282828}.highlight .s2{color:#b8bb26;background-color:#282828}.highlight .se{color:#b8bb26;background-color:#282828}.highlight .sh{color:#b8bb26;background-color:#282828}.highlight .si{color:#b8bb26;background-color:#282828}.highlight .sx{color:#b8bb26;background-color:#282828}.highlight .sr{color:#b8bb26;background-color:#282828}.highlight .s1{color:#b8bb26;background-color:#282828}.highlight .ss{color:#83a598;background-color:#282828}.highlight .bp{color:#fabd2f;background-color:#282828}.highlight .vc{color:#ebdbb2;background-color:#282828}.highlight .vg{color:#ebdbb2;background-color:#282828}.highlight .vi{color:#ebdbb2;background-color:#282828}.highlight .il{color:#d3869b;background-color:#282828}
\ No newline at end of file
diff --git a/src/compat.h b/src/compat.h
new file mode 100644 (file)
index 0000000..f97a69b
--- /dev/null
@@ -0,0 +1,6 @@
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+#undef reallocarray
+void *reallocarray(void *, size_t, size_t);
diff --git a/src/highlight.py b/src/highlight.py
new file mode 100755 (executable)
index 0000000..2b81d8c
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+import pygments
+from pygments import highlight
+from pygments.formatters import HtmlFormatter
+import pygments.lexers
+from markdown import markdown
+
+from sys import stdin, stderr
+
+filename = stdin.readline().strip()
+contents = stdin.read()
+lexer = None
+
+try:
+    lexer = pygments.lexers.guess_lexer_for_filename(filename, contents)
+except pygments.util.ClassNotFound:
+    try:
+        lexer = pygments.lexers.guess_lexer(contents)
+    except pygments.util.ClassNotFound:
+        pass
+
+if lexer is None:
+    lexer = pygments.lexers.special.TextLexer
+
+rendered = (
+    markdown(
+        contents,
+        extensions=[
+            "codehilite",
+            "extra",
+            "sane_lists",
+            "smarty",
+            "pymdownx.tasklist",
+        ],
+    )
+    if lexer.__class__ is pygments.lexers.MarkdownLexer
+    else None
+)
+
+formatter = HtmlFormatter(
+    style="monokai",
+    cssclass="highlight",
+    linenos="table",
+    lineanchors="loc",
+    anchorlinenos=True,
+)
+
+outp = ""
+if rendered:
+    outp += '<article class="markup markdown">'
+    outp += rendered
+    outp += "</article>"
+outp += f'<div id="blob">{highlight(contents, lexer, formatter)}</div>"'
+outp += '<link rel="stylesheet" href="/syntax.css"'
+
+print(outp)
+
+print(f"Filename: {filename}; Lexer: {lexer}.", file=stderr)
+
+if rendered:
+    print("Markdown was rendered in addition.", file=stderr)
diff --git a/src/reallocarray.c b/src/reallocarray.c
new file mode 100644 (file)
index 0000000..b92dae5
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "compat.h"
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW        (1UL << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+       if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+           nmemb > 0 && SIZE_MAX / nmemb < size) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       return realloc(optr, size * nmemb);
+}
diff --git a/src/stagit-index.c b/src/stagit-index.c
new file mode 100644 (file)
index 0000000..2b243ec
--- /dev/null
@@ -0,0 +1,220 @@
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <git2.h>
+
+static git_repository *repo;
+
+static const char *relpath = "";
+
+static char description[255] = "Repositories";
+static char *name = "";
+static char owner[255];
+
+void
+joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
+{
+       int r;
+
+       r = snprintf(buf, bufsiz, "%s%s%s",
+               path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+       if (r < 0 || (size_t)r >= bufsiz)
+               errx(1, "path truncated: '%s%s%s'",
+                       path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0. */
+void
+xmlencode(FILE *fp, const char *s, size_t len)
+{
+       size_t i;
+
+       for (i = 0; *s && i < len; s++, i++) {
+               switch(*s) {
+               case '<':  fputs("&lt;",   fp); break;
+               case '>':  fputs("&gt;",   fp); break;
+               case '\'': fputs("&#39;" , fp); break;
+               case '&':  fputs("&amp;",  fp); break;
+               case '"':  fputs("&quot;", fp); break;
+               default:   fputc(*s, fp);
+               }
+       }
+}
+
+void
+printtimeshort(FILE *fp, const git_time *intime)
+{
+       struct tm *intm;
+       time_t t;
+       char out[32];
+
+       t = (time_t)intime->time;
+       if (!(intm = gmtime(&t)))
+               return;
+       strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
+       fputs(out, fp);
+}
+
+void
+writeheader(FILE *fp)
+{
+       fputs("<!DOCTYPE html>\n"
+               "<html>\n<head>\n"
+               "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
+               "<title>", fp);
+       xmlencode(fp, description, strlen(description));
+       fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
+       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
+       fputs("</head>\n<body>\n", fp);
+       fprintf(fp, "<table>\n<tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>\n"
+               "<td><span class=\"desc\">", relpath);
+       xmlencode(fp, description, strlen(description));
+       fputs("</span></td></tr><tr><td></td><td>\n"
+               "</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n"
+               "<table id=\"index\"><thead>\n"
+               "<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>"
+               "<td><b>Last commit</b></td></tr>"
+               "</thead><tbody>\n", fp);
+}
+
+void
+writefooter(FILE *fp)
+{
+       fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp);
+}
+
+int
+writelog(FILE *fp)
+{
+       git_commit *commit = NULL;
+       const git_signature *author;
+       git_revwalk *w = NULL;
+       git_oid id;
+       char *stripped_name = NULL, *p;
+       int ret = 0;
+
+       git_revwalk_new(&w, repo);
+       git_revwalk_push_head(w);
+       git_revwalk_simplify_first_parent(w);
+
+       if (git_revwalk_next(&id, w) ||
+           git_commit_lookup(&commit, repo, &id)) {
+               ret = -1;
+               goto err;
+       }
+
+       author = git_commit_author(commit);
+
+       /* strip .git suffix */
+       if (!(stripped_name = strdup(name)))
+               err(1, "strdup");
+       if ((p = strrchr(stripped_name, '.')))
+               if (!strcmp(p, ".git"))
+                       *p = '\0';
+
+       fputs("<tr><td><a href=\"", fp);
+       xmlencode(fp, stripped_name, strlen(stripped_name));
+       fputs("/log.html\">", fp);
+       xmlencode(fp, stripped_name, strlen(stripped_name));
+       fputs("</a></td><td>", fp);
+       xmlencode(fp, description, strlen(description));
+       fputs("</td><td>", fp);
+       xmlencode(fp, owner, strlen(owner));
+       fputs("</td><td>", fp);
+       if (author)
+               printtimeshort(fp, &(author->when));
+       fputs("</td></tr>", fp);
+
+       git_commit_free(commit);
+err:
+       git_revwalk_free(w);
+       free(stripped_name);
+
+       return ret;
+}
+
+int
+main(int argc, char *argv[])
+{
+       FILE *fp;
+       char path[PATH_MAX], repodirabs[PATH_MAX + 1];
+       const char *repodir;
+       int i, ret = 0;
+
+       if (argc < 2) {
+               fprintf(stderr, "%s [repodir...]\n", argv[0]);
+               return 1;
+       }
+
+       git_libgit2_init();
+
+#ifdef __OpenBSD__
+       for (i = 1; i < argc; i++)
+               if (unveil(argv[i], "r") == -1)
+                       err(1, "unveil: %s", argv[i]);
+
+       if (pledge("stdio rpath", NULL) == -1)
+               err(1, "pledge");
+#endif
+
+       writeheader(stdout);
+
+       for (i = 1; i < argc; i++) {
+               repodir = argv[i];
+               if (!realpath(repodir, repodirabs))
+                       err(1, "realpath");
+
+               if (git_repository_open_ext(&repo, repodir,
+                   GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
+                       fprintf(stderr, "%s: cannot open repository\n", argv[0]);
+                       ret = 1;
+                       continue;
+               }
+
+               /* use directory name as name */
+               if ((name = strrchr(repodirabs, '/')))
+                       name++;
+               else
+                       name = "";
+
+               /* read description or .git/description */
+               joinpath(path, sizeof(path), repodir, "description");
+               if (!(fp = fopen(path, "r"))) {
+                       joinpath(path, sizeof(path), repodir, ".git/description");
+                       fp = fopen(path, "r");
+               }
+               description[0] = '\0';
+               if (fp) {
+                       if (!fgets(description, sizeof(description), fp))
+                               description[0] = '\0';
+                       fclose(fp);
+               }
+
+               /* read owner or .git/owner */
+               joinpath(path, sizeof(path), repodir, "owner");
+               if (!(fp = fopen(path, "r"))) {
+                       joinpath(path, sizeof(path), repodir, ".git/owner");
+                       fp = fopen(path, "r");
+               }
+               owner[0] = '\0';
+               if (fp) {
+                       if (!fgets(owner, sizeof(owner), fp))
+                               owner[0] = '\0';
+                       owner[strcspn(owner, "\n")] = '\0';
+                       fclose(fp);
+               }
+               writelog(stdout);
+       }
+       writefooter(stdout);
+
+       /* cleanup */
+       git_repository_free(repo);
+       git_libgit2_shutdown();
+
+       return ret;
+}
diff --git a/src/stagit.c b/src/stagit.c
new file mode 100644 (file)
index 0000000..264604e
--- /dev/null
@@ -0,0 +1,1319 @@
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <git2.h>
+
+#include "compat.h"
+
+struct deltainfo {
+       git_patch *patch;
+
+       size_t addcount;
+       size_t delcount;
+};
+
+struct commitinfo {
+       const git_oid *id;
+
+       char oid[GIT_OID_HEXSZ + 1];
+       char parentoid[GIT_OID_HEXSZ + 1];
+
+       const git_signature *author;
+       const git_signature *committer;
+       const char          *summary;
+       const char          *msg;
+
+       git_diff   *diff;
+       git_commit *commit;
+       git_commit *parent;
+       git_tree   *commit_tree;
+       git_tree   *parent_tree;
+
+       size_t addcount;
+       size_t delcount;
+       size_t filecount;
+
+       struct deltainfo **deltas;
+       size_t ndeltas;
+};
+
+static git_repository *repo;
+
+static const char *relpath = "";
+static const char *repodir;
+
+static char *name = "";
+static char *strippedname = "";
+static char description[255];
+static char cloneurl[1024];
+static char *submodules;
+static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" };
+static char *license;
+static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" };
+static char *readme;
+static long long nlogcommits = -1; /* < 0 indicates not used */
+
+/* cache */
+static git_oid lastoid;
+static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
+static FILE *rcachefp, *wcachefp;
+static const char *cachefile;
+
+int cp(char fileSource[], char fileDestination[])
+{
+    int c;
+    FILE *stream_R, *stream_W; 
+
+    stream_R = fopen(fileSource, "r");
+    if (stream_R == NULL)
+        return -1;
+    stream_W = fopen(fileDestination, "w");   //create and write to file
+    if (stream_W == NULL)
+     {
+        fclose(stream_R);
+        return -2;
+     }    
+    while ((c = fgetc(stream_R)) != EOF)
+        fputc(c, stream_W);
+    fclose(stream_R);
+    fclose(stream_W);
+
+    return 0;
+}
+
+void
+joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
+{
+       int r;
+
+       r = snprintf(buf, bufsiz, "%s%s%s",
+               path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+       if (r < 0 || (size_t)r >= bufsiz)
+               errx(1, "path truncated: '%s%s%s'",
+                       path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
+}
+
+void
+deltainfo_free(struct deltainfo *di)
+{
+       if (!di)
+               return;
+       git_patch_free(di->patch);
+       memset(di, 0, sizeof(*di));
+       free(di);
+}
+
+int
+commitinfo_getstats(struct commitinfo *ci)
+{
+       struct deltainfo *di;
+       git_diff_options opts;
+       git_diff_find_options fopts;
+       const git_diff_delta *delta;
+       const git_diff_hunk *hunk;
+       const git_diff_line *line;
+       git_patch *patch = NULL;
+       size_t ndeltas, nhunks, nhunklines;
+       size_t i, j, k;
+
+       if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
+               goto err;
+       if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
+               if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
+                       ci->parent = NULL;
+                       ci->parent_tree = NULL;
+               }
+       }
+
+       git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
+       opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
+                     GIT_DIFF_IGNORE_SUBMODULES |
+                     GIT_DIFF_INCLUDE_TYPECHANGE;
+       if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
+               goto err;
+
+       if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
+               goto err;
+       /* find renames and copies, exact matches (no heuristic) for renames. */
+       fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
+                      GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+       if (git_diff_find_similar(ci->diff, &fopts))
+               goto err;
+
+       ndeltas = git_diff_num_deltas(ci->diff);
+       if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
+               err(1, "calloc");
+
+       for (i = 0; i < ndeltas; i++) {
+               if (git_patch_from_diff(&patch, ci->diff, i))
+                       goto err;
+
+               if (!(di = calloc(1, sizeof(struct deltainfo))))
+                       err(1, "calloc");
+               di->patch = patch;
+               ci->deltas[i] = di;
+
+               delta = git_patch_get_delta(patch);
+
+               /* skip stats for binary data */
+               if (delta->flags & GIT_DIFF_FLAG_BINARY)
+                       continue;
+
+               nhunks = git_patch_num_hunks(patch);
+               for (j = 0; j < nhunks; j++) {
+                       if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
+                               break;
+                       for (k = 0; ; k++) {
+                               if (git_patch_get_line_in_hunk(&line, patch, j, k))
+                                       break;
+                               if (line->old_lineno == -1) {
+                                       di->addcount++;
+                                       ci->addcount++;
+                               } else if (line->new_lineno == -1) {
+                                       di->delcount++;
+                                       ci->delcount++;
+                               }
+                       }
+               }
+       }
+       ci->ndeltas = i;
+       ci->filecount = i;
+
+       return 0;
+
+err:
+       git_diff_free(ci->diff);
+       ci->diff = NULL;
+       git_tree_free(ci->commit_tree);
+       ci->commit_tree = NULL;
+       git_tree_free(ci->parent_tree);
+       ci->parent_tree = NULL;
+       git_commit_free(ci->parent);
+       ci->parent = NULL;
+
+       if (ci->deltas)
+               for (i = 0; i < ci->ndeltas; i++)
+                       deltainfo_free(ci->deltas[i]);
+       free(ci->deltas);
+       ci->deltas = NULL;
+       ci->ndeltas = 0;
+       ci->addcount = 0;
+       ci->delcount = 0;
+       ci->filecount = 0;
+
+       return -1;
+}
+
+void
+commitinfo_free(struct commitinfo *ci)
+{
+       size_t i;
+
+       if (!ci)
+               return;
+       if (ci->deltas)
+               for (i = 0; i < ci->ndeltas; i++)
+                       deltainfo_free(ci->deltas[i]);
+
+       free(ci->deltas);
+       git_diff_free(ci->diff);
+       git_tree_free(ci->commit_tree);
+       git_tree_free(ci->parent_tree);
+       git_commit_free(ci->commit);
+       git_commit_free(ci->parent);
+       memset(ci, 0, sizeof(*ci));
+       free(ci);
+}
+
+struct commitinfo *
+commitinfo_getbyoid(const git_oid *id)
+{
+       struct commitinfo *ci;
+
+       if (!(ci = calloc(1, sizeof(struct commitinfo))))
+               err(1, "calloc");
+
+       if (git_commit_lookup(&(ci->commit), repo, id))
+               goto err;
+       ci->id = id;
+
+       git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
+       git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
+
+       ci->author = git_commit_author(ci->commit);
+       ci->committer = git_commit_committer(ci->commit);
+       ci->summary = git_commit_summary(ci->commit);
+       ci->msg = git_commit_message(ci->commit);
+
+       return ci;
+
+err:
+       commitinfo_free(ci);
+
+       return NULL;
+}
+
+FILE *
+efopen(const char *name, const char *flags)
+{
+       FILE *fp;
+
+       if (!(fp = fopen(name, flags)))
+               err(1, "fopen: '%s'", name);
+
+       return fp;
+}
+
+/* Escape characters below as HTML 2.0 / XML 1.0. */
+void
+xmlencode(FILE *fp, const char *s, size_t len)
+{
+       size_t i;
+
+       for (i = 0; *s && i < len; s++, i++) {
+               switch(*s) {
+               case '<':  fputs("&lt;",   fp); break;
+               case '>':  fputs("&gt;",   fp); break;
+               case '\'': fputs("&#39;",  fp); break;
+               case '&':  fputs("&amp;",  fp); break;
+               case '"':  fputs("&quot;", fp); break;
+               default:   fputc(*s, fp);
+               }
+       }
+}
+
+int
+mkdirp(const char *path)
+{
+       char tmp[PATH_MAX], *p;
+
+       if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
+               errx(1, "path truncated: '%s'", path);
+       for (p = tmp + (tmp[0] == '/'); *p; p++) {
+               if (*p != '/')
+                       continue;
+               *p = '\0';
+               if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
+                       return -1;
+               *p = '/';
+       }
+       if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
+               return -1;
+       return 0;
+}
+
+void
+printtimez(FILE *fp, const git_time *intime)
+{
+       struct tm *intm;
+       time_t t;
+       char out[32];
+
+       t = (time_t)intime->time;
+       if (!(intm = gmtime(&t)))
+               return;
+       strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
+       fputs(out, fp);
+}
+
+void
+printtime(FILE *fp, const git_time *intime)
+{
+       struct tm *intm;
+       time_t t;
+       char out[32];
+
+       t = (time_t)intime->time + (intime->offset * 60);
+       if (!(intm = gmtime(&t)))
+               return;
+       strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
+       if (intime->offset < 0)
+               fprintf(fp, "%s -%02d%02d", out,
+                           -(intime->offset) / 60, -(intime->offset) % 60);
+       else
+               fprintf(fp, "%s +%02d%02d", out,
+                           intime->offset / 60, intime->offset % 60);
+}
+
+void
+printtimeshort(FILE *fp, const git_time *intime)
+{
+       struct tm *intm;
+       time_t t;
+       char out[32];
+
+       t = (time_t)intime->time;
+       if (!(intm = gmtime(&t)))
+               return;
+       strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
+       fputs(out, fp);
+}
+
+void
+writeheader(FILE *fp, const char *title)
+{
+       fputs("<!DOCTYPE html>\n"
+               "<html>\n<head>\n"
+               "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
+               "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n"
+               "<title>", fp);
+       xmlencode(fp, title, strlen(title));
+       if (title[0] && strippedname[0])
+               fputs(" - ", fp);
+       xmlencode(fp, strippedname, strlen(strippedname));
+       if (description[0])
+               fputs(" - ", fp);
+       xmlencode(fp, description, strlen(description));
+       fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
+       fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
+               name, relpath);
+       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.min.css\" />\n", relpath);
+       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%ssyntax.css\" />\n", relpath);
+       fputs("</head>\n<body>\n<table><tr><td>", fp);
+       fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
+               relpath, relpath);
+       fputs("</td><td><h1>", fp);
+       xmlencode(fp, strippedname, strlen(strippedname));
+       fputs("</h1><span class=\"desc\">", fp);
+       xmlencode(fp, description, strlen(description));
+       fputs("</span></td></tr>", fp);
+       if (cloneurl[0]) {
+               fputs("<tr class=\"url\"><td></td><td><span class=\"clone\">git clone <a href=\"", fp);
+               xmlencode(fp, cloneurl, strlen(cloneurl));
+               fputs("\">", fp);
+               xmlencode(fp, cloneurl, strlen(cloneurl));
+               fputs("</a></span></td></tr>", fp);
+       }
+       fputs("<tr><td></td><td>\n", fp);
+       fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
+       fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath);
+       fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
+       if (submodules)
+               fprintf(fp, " | <a href=\"%sfile/%s.html\">Submodules</a>",
+                       relpath, submodules);
+       if (readme)
+               fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>",
+                       relpath, readme);
+       if (license)
+               fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>",
+                       relpath, license);
+       fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp);
+}
+
+void
+writefooter(FILE *fp)
+{
+       fputs("</div>\n</body>\n</html>\n", fp);
+}
+
+int
+call_py(const char *filename, FILE *fp, const char *s, size_t len)
+{
+       // Flush HTML-file
+       fflush(fp);
+       // Copy STDOUT
+       int stdout_copy = dup(1);
+       // Redirect STDOUT
+       dup2(fileno(fp), 1);
+
+       // Python Pygments script for syntax highlighting.
+       FILE *child = popen("/usr/local/share/stagit/highlight.py", "w");
+       if (child == NULL) {
+               printf("child is null: %s", strerror(errno));
+               exit(1);
+       }
+       // Give filename through STDIN:
+       fprintf(child, "%s\n", filename);
+       // Give code to highlight through STDIN:
+       int lc;
+       size_t i;
+       for (i = 0; *s && i < len; s++, i++) {
+               if (*s == '\n') lc++;
+               fprintf(child, "%c", *s);
+       }
+
+       pclose(child);
+       fflush(stdout);
+       // Give back STDOUT.
+       dup2(stdout_copy, 1);
+       return lc;
+}
+
+int
+writeblobhtml(const char *filename, FILE *fp, const git_blob *blob)
+{
+       int lc = 0;
+       const char *s = git_blob_rawcontent(blob);
+       git_off_t len = git_blob_rawsize(blob);
+
+       if (len > 0) {
+               lc = call_py(filename, fp, s, len);
+       }
+
+       return lc;
+}
+
+void
+printcommit(FILE *fp, struct commitinfo *ci)
+{
+       fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
+               relpath, ci->oid, ci->oid);
+
+       if (ci->parentoid[0])
+               fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
+                       relpath, ci->parentoid, ci->parentoid);
+
+       if (ci->author) {
+               fputs("<b>Author:</b> ", fp);
+               xmlencode(fp, ci->author->name, strlen(ci->author->name));
+               fputs(" &lt;<a href=\"mailto:", fp);
+               xmlencode(fp, ci->author->email, strlen(ci->author->email));
+               fputs("\">", fp);
+               xmlencode(fp, ci->author->email, strlen(ci->author->email));
+               fputs("</a>&gt;\n<b>Date:</b>   ", fp);
+               printtime(fp, &(ci->author->when));
+               fputc('\n', fp);
+       }
+       if (ci->msg) {
+               fputc('\n', fp);
+               xmlencode(fp, ci->msg, strlen(ci->msg));
+               fputc('\n', fp);
+       }
+}
+
+void
+printshowfile(FILE *fp, struct commitinfo *ci)
+{
+       const git_diff_delta *delta;
+       const git_diff_hunk *hunk;
+       const git_diff_line *line;
+       git_patch *patch;
+       size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
+       char linestr[80];
+       int c;
+
+       printcommit(fp, ci);
+
+       if (!ci->deltas)
+               return;
+
+       if (ci->filecount > 1000   ||
+           ci->ndeltas   > 1000   ||
+           ci->addcount  > 100000 ||
+           ci->delcount  > 100000) {
+               fputs("Diff is too large, output suppressed.\n", fp);
+               return;
+       }
+
+       /* diff stat */
+       fputs("<b>Diffstat:</b>\n<table>", fp);
+       for (i = 0; i < ci->ndeltas; i++) {
+               delta = git_patch_get_delta(ci->deltas[i]->patch);
+
+               switch (delta->status) {
+               case GIT_DELTA_ADDED:      c = 'A'; break;
+               case GIT_DELTA_COPIED:     c = 'C'; break;
+               case GIT_DELTA_DELETED:    c = 'D'; break;
+               case GIT_DELTA_MODIFIED:   c = 'M'; break;
+               case GIT_DELTA_RENAMED:    c = 'R'; break;
+               case GIT_DELTA_TYPECHANGE: c = 'T'; break;
+               default:                   c = ' '; break;
+               }
+               if (c == ' ')
+                       fprintf(fp, "<tr><td>%c", c);
+               else
+                       fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
+
+               fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
+               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+               if (strcmp(delta->old_file.path, delta->new_file.path)) {
+                       fputs(" -&gt; ", fp);
+                       xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+               }
+
+               add = ci->deltas[i]->addcount;
+               del = ci->deltas[i]->delcount;
+               changed = add + del;
+               total = sizeof(linestr) - 2;
+               if (changed > total) {
+                       if (add)
+                               add = ((float)total / changed * add) + 1;
+                       if (del)
+                               del = ((float)total / changed * del) + 1;
+               }
+               memset(&linestr, '+', add);
+               memset(&linestr[add], '-', del);
+
+               fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
+                       ci->deltas[i]->addcount + ci->deltas[i]->delcount);
+               fwrite(&linestr, 1, add, fp);
+               fputs("</span><span class=\"d\">", fp);
+               fwrite(&linestr[add], 1, del, fp);
+               fputs("</span></td></tr>\n", fp);
+       }
+       fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
+               ci->filecount, ci->filecount == 1 ? "" : "s",
+               ci->addcount,  ci->addcount  == 1 ? "" : "s",
+               ci->delcount,  ci->delcount  == 1 ? "" : "s");
+
+       fputs("<hr/>", fp);
+
+       for (i = 0; i < ci->ndeltas; i++) {
+               patch = ci->deltas[i]->patch;
+               delta = git_patch_get_delta(patch);
+               fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
+               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+               fputs(".html\">", fp);
+               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
+               fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
+               xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+               fprintf(fp, ".html\">");
+               xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
+               fprintf(fp, "</a></b>\n");
+
+               /* check binary data */
+               if (delta->flags & GIT_DIFF_FLAG_BINARY) {
+                       fputs("Binary files differ.\n", fp);
+                       continue;
+               }
+
+               nhunks = git_patch_num_hunks(patch);
+               for (j = 0; j < nhunks; j++) {
+                       if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
+                               break;
+
+                       fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
+                       xmlencode(fp, hunk->header, hunk->header_len);
+                       fputs("</a>", fp);
+
+                       for (k = 0; ; k++) {
+                               if (git_patch_get_line_in_hunk(&line, patch, j, k))
+                                       break;
+                               if (line->old_lineno == -1)
+                                       fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
+                                               i, j, k, i, j, k);
+                               else if (line->new_lineno == -1)
+                                       fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
+                                               i, j, k, i, j, k);
+                               else
+                                       fputc(' ', fp);
+                               xmlencode(fp, line->content, line->content_len);
+                               if (line->old_lineno == -1 || line->new_lineno == -1)
+                                       fputs("</a>", fp);
+                       }
+               }
+       }
+}
+
+void
+writelogline(FILE *fp, struct commitinfo *ci)
+{
+       fputs("<tr><td>", fp);
+       if (ci->author)
+               printtimeshort(fp, &(ci->author->when));
+       fputs("</td><td>", fp);
+       if (ci->summary) {
+               fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
+               xmlencode(fp, ci->summary, strlen(ci->summary));
+               fputs("</a>", fp);
+       }
+       fputs("</td><td>", fp);
+       if (ci->author)
+               xmlencode(fp, ci->author->name, strlen(ci->author->name));
+       fputs("</td><td class=\"num\" align=\"right\">", fp);
+       fprintf(fp, "%zu", ci->filecount);
+       fputs("</td><td class=\"num\" align=\"right\">", fp);
+       fprintf(fp, "+%zu", ci->addcount);
+       fputs("</td><td class=\"num\" align=\"right\">", fp);
+       fprintf(fp, "-%zu", ci->delcount);
+       fputs("</td></tr>\n", fp);
+}
+
+int
+writelog(FILE *fp, const git_oid *oid)
+{
+       struct commitinfo *ci;
+       git_revwalk *w = NULL;
+       git_oid id;
+       char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
+       FILE *fpfile;
+       int r;
+
+       git_revwalk_new(&w, repo);
+       git_revwalk_push(w, oid);
+       git_revwalk_simplify_first_parent(w);
+
+       while (!git_revwalk_next(&id, w)) {
+               relpath = "";
+
+               if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
+                       break;
+
+               git_oid_tostr(oidstr, sizeof(oidstr), &id);
+               r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
+               if (r < 0 || (size_t)r >= sizeof(path))
+                       errx(1, "path truncated: 'commit/%s.html'", oidstr);
+               r = access(path, F_OK);
+
+               /* optimization: if there are no log lines to write and
+                  the commit file already exists: skip the diffstat */
+               if (!nlogcommits && !r)
+                       continue;
+
+               if (!(ci = commitinfo_getbyoid(&id)))
+                       break;
+               /* diffstat: for stagit HTML required for the log.html line */
+               if (commitinfo_getstats(ci) == -1)
+                       goto err;
+
+               if (nlogcommits < 0) {
+                       writelogline(fp, ci);
+               } else if (nlogcommits > 0) {
+                       writelogline(fp, ci);
+                       nlogcommits--;
+                       if (!nlogcommits && ci->parentoid[0])
+                               fputs("<tr><td></td><td colspan=\"5\">"
+                                     "More commits remaining [...]</td>"
+                                     "</tr>\n", fp);
+               }
+
+               if (cachefile)
+                       writelogline(wcachefp, ci);
+
+               /* check if file exists if so skip it */
+               if (r) {
+                       relpath = "../";
+                       fpfile = efopen(path, "w");
+                       writeheader(fpfile, ci->summary);
+                       fputs("<pre>", fpfile);
+                       printshowfile(fpfile, ci);
+                       fputs("</pre>\n", fpfile);
+                       writefooter(fpfile);
+                       fclose(fpfile);
+               }
+err:
+               commitinfo_free(ci);
+       }
+       git_revwalk_free(w);
+
+       relpath = "";
+
+       return 0;
+}
+
+void
+printcommitatom(FILE *fp, struct commitinfo *ci)
+{
+       fputs("<entry>\n", fp);
+
+       fprintf(fp, "<id>%s</id>\n", ci->oid);
+       if (ci->author) {
+               fputs("<published>", fp);
+               printtimez(fp, &(ci->author->when));
+               fputs("</published>\n", fp);
+       }
+       if (ci->committer) {
+               fputs("<updated>", fp);
+               printtimez(fp, &(ci->committer->when));
+               fputs("</updated>\n", fp);
+       }
+       if (ci->summary) {
+               fputs("<title type=\"text\">", fp);
+               xmlencode(fp, ci->summary, strlen(ci->summary));
+               fputs("</title>\n", fp);
+       }
+       fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.html\" />\n",
+               ci->oid);
+
+       if (ci->author) {
+               fputs("<author>\n<name>", fp);
+               xmlencode(fp, ci->author->name, strlen(ci->author->name));
+               fputs("</name>\n<email>", fp);
+               xmlencode(fp, ci->author->email, strlen(ci->author->email));
+               fputs("</email>\n</author>\n", fp);
+       }
+
+       fputs("<content type=\"text\">", fp);
+       fprintf(fp, "commit %s\n", ci->oid);
+       if (ci->parentoid[0])
+               fprintf(fp, "parent %s\n", ci->parentoid);
+       if (ci->author) {
+               fputs("Author: ", fp);
+               xmlencode(fp, ci->author->name, strlen(ci->author->name));
+               fputs(" &lt;", fp);
+               xmlencode(fp, ci->author->email, strlen(ci->author->email));
+               fputs("&gt;\nDate:   ", fp);
+               printtime(fp, &(ci->author->when));
+               fputc('\n', fp);
+       }
+       if (ci->msg) {
+               fputc('\n', fp);
+               xmlencode(fp, ci->msg, strlen(ci->msg));
+       }
+       fputs("\n</content>\n</entry>\n", fp);
+}
+
+int
+writeatom(FILE *fp)
+{
+       struct commitinfo *ci;
+       git_revwalk *w = NULL;
+       git_oid id;
+       size_t i, m = 100; /* last 'm' commits */
+
+       fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+             "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
+       xmlencode(fp, strippedname, strlen(strippedname));
+       fputs(", branch HEAD</title>\n<subtitle>", fp);
+       xmlencode(fp, description, strlen(description));
+       fputs("</subtitle>\n", fp);
+
+       git_revwalk_new(&w, repo);
+       git_revwalk_push_head(w);
+       git_revwalk_simplify_first_parent(w);
+
+       for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
+               if (!(ci = commitinfo_getbyoid(&id)))
+                       break;
+               printcommitatom(fp, ci);
+               commitinfo_free(ci);
+       }
+       git_revwalk_free(w);
+
+       fputs("</feed>\n", fp);
+
+       return 0;
+}
+
+int
+writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t filesize)
+{
+       char tmp[PATH_MAX] = "", *d;
+       const char *p;
+       int lc = 0;
+       FILE *fp;
+
+       if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
+               errx(1, "path truncated: '%s'", fpath);
+       if (!(d = dirname(tmp)))
+               err(1, "dirname");
+       if (mkdirp(d))
+               return -1;
+
+       for (p = fpath, tmp[0] = '\0'; *p; p++) {
+               if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
+                       errx(1, "path truncated: '../%s'", tmp);
+       }
+       relpath = tmp;
+
+       fp = efopen(fpath, "w");
+       writeheader(fp, filename);
+       fputs("<p> ", fp);
+       xmlencode(fp, filename, strlen(filename));
+       fprintf(fp, " (%juB)", (uintmax_t)filesize);
+       fputs("</p><hr/>", fp);
+
+       if (git_blob_is_binary((git_blob *)obj)) {
+               fputs("<p>Binary file.</p>\n", fp);
+       } else {
+               lc = writeblobhtml(filename, fp, (git_blob *)obj);
+               if (ferror(fp))
+                       err(1, "fwrite");
+       }
+       writefooter(fp);
+       fclose(fp);
+
+       relpath = "";
+
+       return lc;
+}
+
+const char *
+filemode(git_filemode_t m)
+{
+       static char mode[11];
+
+       memset(mode, '-', sizeof(mode) - 1);
+       mode[10] = '\0';
+
+       if (S_ISREG(m))
+               mode[0] = '-';
+       else if (S_ISBLK(m))
+               mode[0] = 'b';
+       else if (S_ISCHR(m))
+               mode[0] = 'c';
+       else if (S_ISDIR(m))
+               mode[0] = 'd';
+       else if (S_ISFIFO(m))
+               mode[0] = 'p';
+       else if (S_ISLNK(m))
+               mode[0] = 'l';
+       else if (S_ISSOCK(m))
+               mode[0] = 's';
+       else
+               mode[0] = '?';
+
+       if (m & S_IRUSR) mode[1] = 'r';
+       if (m & S_IWUSR) mode[2] = 'w';
+       if (m & S_IXUSR) mode[3] = 'x';
+       if (m & S_IRGRP) mode[4] = 'r';
+       if (m & S_IWGRP) mode[5] = 'w';
+       if (m & S_IXGRP) mode[6] = 'x';
+       if (m & S_IROTH) mode[7] = 'r';
+       if (m & S_IWOTH) mode[8] = 'w';
+       if (m & S_IXOTH) mode[9] = 'x';
+
+       if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
+       if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
+       if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
+
+       return mode;
+}
+
+int
+writefilestree(FILE *fp, git_tree *tree, const char *path)
+{
+       const git_tree_entry *entry = NULL;
+       git_submodule *module = NULL;
+       git_object *obj = NULL;
+       git_off_t filesize;
+       const char *entryname;
+       char filepath[PATH_MAX], entrypath[PATH_MAX];
+       size_t count, i;
+       int lc, r, ret;
+
+       count = git_tree_entrycount(tree);
+       for (i = 0; i < count; i++) {
+               if (!(entry = git_tree_entry_byindex(tree, i)) ||
+                   !(entryname = git_tree_entry_name(entry)))
+                       return -1;
+               joinpath(entrypath, sizeof(entrypath), path, entryname);
+
+               r = snprintf(filepath, sizeof(filepath), "file/%s.html",
+                        entrypath);
+               if (r < 0 || (size_t)r >= sizeof(filepath))
+                       errx(1, "path truncated: 'file/%s.html'", entrypath);
+
+               if (!git_tree_entry_to_object(&obj, repo, entry)) {
+                       switch (git_object_type(obj)) {
+                       case GIT_OBJ_BLOB:
+                               break;
+                       case GIT_OBJ_TREE:
+                               /* NOTE: recurses */
+                               ret = writefilestree(fp, (git_tree *)obj,
+                                                    entrypath);
+                               git_object_free(obj);
+                               if (ret)
+                                       return ret;
+                               continue;
+                       default:
+                               git_object_free(obj);
+                               continue;
+                       }
+
+                       filesize = git_blob_rawsize((git_blob *)obj);
+                       lc = writeblob(obj, filepath, entryname, filesize);
+
+                       fputs("<tr><td>", fp);
+                       fputs(filemode(git_tree_entry_filemode(entry)), fp);
+                       fprintf(fp, "</td><td><a href=\"%s", relpath);
+                       xmlencode(fp, filepath, strlen(filepath));
+                       fputs("\">", fp);
+                       xmlencode(fp, entrypath, strlen(entrypath));
+                       fputs("</a></td><td class=\"num\" align=\"right\">", fp);
+                       if (lc > 0)
+                               fprintf(fp, "%dL", lc);
+                       else
+                               fprintf(fp, "%juB", (uintmax_t)filesize);
+                       fputs("</td></tr>\n", fp);
+                       git_object_free(obj);
+               } else if (!git_submodule_lookup(&module, repo, entryname)) {
+                       fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
+                               relpath);
+                       xmlencode(fp, entrypath, strlen(entrypath));
+                       git_submodule_free(module);
+                       fputs("</a></td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
+               }
+       }
+
+       return 0;
+}
+
+int
+writefiles(FILE *fp, const git_oid *id)
+{
+       git_tree *tree = NULL;
+       git_commit *commit = NULL;
+       int ret = -1;
+
+       fputs("<table id=\"files\"><thead>\n<tr>"
+             "<td><b>Mode</b></td><td><b>Name</b></td>"
+             "<td class=\"num\" align=\"right\"><b>Size</b></td>"
+             "</tr>\n</thead><tbody>\n", fp);
+
+       if (!git_commit_lookup(&commit, repo, id) &&
+           !git_commit_tree(&tree, commit))
+               ret = writefilestree(fp, tree, "");
+
+       fputs("</tbody></table>", fp);
+
+       git_commit_free(commit);
+       git_tree_free(tree);
+
+       return ret;
+}
+
+int
+refs_cmp(const void *v1, const void *v2)
+{
+       git_reference *r1 = (*(git_reference **)v1);
+       git_reference *r2 = (*(git_reference **)v2);
+       int r;
+
+       if ((r = git_reference_is_branch(r1) - git_reference_is_branch(r2)))
+               return r;
+
+       return strcmp(git_reference_shorthand(r1),
+                     git_reference_shorthand(r2));
+}
+
+int
+writerefs(FILE *fp)
+{
+       struct commitinfo *ci;
+       const git_oid *id = NULL;
+       git_object *obj = NULL;
+       git_reference *dref = NULL, *r, *ref = NULL;
+       git_reference_iterator *it = NULL;
+       git_reference **refs = NULL;
+       size_t count, i, j, refcount;
+       const char *titles[] = { "Branches", "Tags" };
+       const char *ids[] = { "branches", "tags" };
+       const char *name;
+
+       if (git_reference_iterator_new(&it, repo))
+               return -1;
+
+       for (refcount = 0; !git_reference_next(&ref, it); refcount++) {
+               if (!(refs = reallocarray(refs, refcount + 1, sizeof(git_reference *))))
+                       err(1, "realloc");
+               refs[refcount] = ref;
+       }
+       git_reference_iterator_free(it);
+
+       /* sort by type then shorthand name */
+       qsort(refs, refcount, sizeof(git_reference *), refs_cmp);
+
+       for (j = 0; j < 2; j++) {
+               for (i = 0, count = 0; i < refcount; i++) {
+                       if (!(git_reference_is_branch(refs[i]) && j == 0) &&
+                           !(git_reference_is_tag(refs[i]) && j == 1))
+                               continue;
+
+                       switch (git_reference_type(refs[i])) {
+                       case GIT_REF_SYMBOLIC:
+                               if (git_reference_resolve(&dref, refs[i]))
+                                       goto err;
+                               r = dref;
+                               break;
+                       case GIT_REF_OID:
+                               r = refs[i];
+                               break;
+                       default:
+                               continue;
+                       }
+                       if (!git_reference_target(r) ||
+                           git_reference_peel(&obj, r, GIT_OBJ_ANY))
+                               goto err;
+                       if (!(id = git_object_id(obj)))
+                               goto err;
+                       if (!(ci = commitinfo_getbyoid(id)))
+                               break;
+
+                       /* print header if it has an entry (first). */
+                       if (++count == 1) {
+                               fprintf(fp, "<h2>%s</h2><table id=\"%s\">"
+                                       "<thead>\n<tr><td><b>Name</b></td>"
+                                       "<td><b>Last commit date</b></td>"
+                                       "<td><b>Author</b></td>\n</tr>\n"
+                                       "</thead><tbody>\n",
+                                        titles[j], ids[j]);
+                       }
+
+                       relpath = "";
+                       name = git_reference_shorthand(r);
+
+                       fputs("<tr><td>", fp);
+                       xmlencode(fp, name, strlen(name));
+                       fputs("</td><td>", fp);
+                       if (ci->author)
+                               printtimeshort(fp, &(ci->author->when));
+                       fputs("</td><td>", fp);
+                       if (ci->author)
+                               xmlencode(fp, ci->author->name, strlen(ci->author->name));
+                       fputs("</td></tr>\n", fp);
+
+                       relpath = "../";
+
+                       commitinfo_free(ci);
+                       git_object_free(obj);
+                       obj = NULL;
+                       git_reference_free(dref);
+                       dref = NULL;
+               }
+               /* table footer */
+               if (count)
+                       fputs("</tbody></table><br/>", fp);
+       }
+
+err:
+       git_object_free(obj);
+       git_reference_free(dref);
+
+       for (i = 0; i < refcount; i++)
+               git_reference_free(refs[i]);
+       free(refs);
+
+       return 0;
+}
+
+void
+usage(char *argv0)
+{
+       fprintf(stderr, "%s [-c cachefile | -l commits] repodir\n", argv0);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       git_object *obj = NULL;
+       const git_oid *head = NULL;
+       mode_t mask;
+       FILE *fp, *fpread;
+       char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
+       char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
+       size_t n;
+       int i, fd;
+
+       for (i = 1; i < argc; i++) {
+               if (argv[i][0] != '-') {
+                       if (repodir)
+                               usage(argv[0]);
+                       repodir = argv[i];
+               } else if (argv[i][1] == 'c') {
+                       if (nlogcommits > 0 || i + 1 >= argc)
+                               usage(argv[0]);
+                       cachefile = argv[++i];
+               } else if (argv[i][1] == 'l') {
+                       if (cachefile || i + 1 >= argc)
+                               usage(argv[0]);
+                       errno = 0;
+                       nlogcommits = strtoll(argv[++i], &p, 10);
+                       if (argv[i][0] == '\0' || *p != '\0' ||
+                           nlogcommits <= 0 || errno)
+                               usage(argv[0]);
+               }
+       }
+       if (!repodir)
+               usage(argv[0]);
+
+       if (!realpath(repodir, repodirabs))
+               err(1, "realpath");
+
+       git_libgit2_init();
+
+#ifdef __OpenBSD__
+       if (unveil(repodir, "r") == -1)
+               err(1, "unveil: %s", repodir);
+       if (unveil(".", "rwc") == -1)
+               err(1, "unveil: .");
+       if (cachefile && unveil(cachefile, "rwc") == -1)
+               err(1, "unveil: %s", cachefile);
+
+       if (cachefile) {
+               if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
+                       err(1, "pledge");
+       } else {
+               if (pledge("stdio rpath wpath cpath", NULL) == -1)
+                       err(1, "pledge");
+       }
+#endif
+
+       if (git_repository_open_ext(&repo, repodir,
+               GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
+               fprintf(stderr, "%s: cannot open repository\n", argv[0]);
+               return 1;
+       }
+
+       /* find HEAD */
+       if (!git_revparse_single(&obj, repo, "HEAD"))
+               head = git_object_id(obj);
+       git_object_free(obj);
+
+       /* use directory name as name */
+       if ((name = strrchr(repodirabs, '/')))
+               name++;
+       else
+               name = "";
+
+       /* copy css */
+       char cwd[PATH_MAX];
+       strcpy(cwd, getcwd(cwd, sizeof(cwd)));
+       cp("/usr/local/share/stagit/syntax.css", strcat(cwd, "/syntax.css"));
+       strcpy(cwd, getcwd(cwd, sizeof(cwd)));
+       cp("/usr/local/share/stagit/style.min.css", strcat(cwd, "/style.min.css"));
+
+       /* strip .git suffix */
+       if (!(strippedname = strdup(name)))
+               err(1, "strdup");
+       if ((p = strrchr(strippedname, '.')))
+               if (!strcmp(p, ".git"))
+                       *p = '\0';
+
+       /* read description or .git/description */
+       joinpath(path, sizeof(path), repodir, "description");
+       if (!(fpread = fopen(path, "r"))) {
+               joinpath(path, sizeof(path), repodir, ".git/description");
+               fpread = fopen(path, "r");
+       }
+       if (fpread) {
+               if (!fgets(description, sizeof(description), fpread))
+                       description[0] = '\0';
+               fclose(fpread);
+       }
+
+       /* read url or .git/url */
+       joinpath(path, sizeof(path), repodir, "url");
+       if (!(fpread = fopen(path, "r"))) {
+               joinpath(path, sizeof(path), repodir, ".git/url");
+               fpread = fopen(path, "r");
+       }
+       if (fpread) {
+               if (!fgets(cloneurl, sizeof(cloneurl), fpread))
+                       cloneurl[0] = '\0';
+               cloneurl[strcspn(cloneurl, "\n")] = '\0';
+               fclose(fpread);
+       }
+
+       /* check LICENSE */
+       for (i = 0; i < sizeof(licensefiles) / sizeof(*licensefiles) && !license; i++) {
+               if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
+                   git_object_type(obj) == GIT_OBJ_BLOB)
+                       license = licensefiles[i] + strlen("HEAD:");
+               git_object_free(obj);
+       }
+
+       /* check README */
+       for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles) && !readme; i++) {
+               if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
+                   git_object_type(obj) == GIT_OBJ_BLOB)
+                       readme = readmefiles[i] + strlen("HEAD:");
+               git_object_free(obj);
+       }
+
+       if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
+           git_object_type(obj) == GIT_OBJ_BLOB)
+               submodules = ".gitmodules";
+       git_object_free(obj);
+
+       /* log for HEAD */
+       fp = efopen("log.html", "w");
+       relpath = "";
+       mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
+       writeheader(fp, "Log");
+       fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
+             "<td><b>Commit</b></td>"
+             "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>"
+             "<td class=\"num\" align=\"right\"><b>+</b></td>"
+             "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
+
+       if (cachefile && head) {
+               /* read from cache file (does not need to exist) */
+               if ((rcachefp = fopen(cachefile, "r"))) {
+                       if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
+                               errx(1, "%s: no object id", cachefile);
+                       if (git_oid_fromstr(&lastoid, lastoidstr))
+                               errx(1, "%s: invalid object id", cachefile);
+               }
+
+               /* write log to (temporary) cache */
+               if ((fd = mkstemp(tmppath)) == -1)
+                       err(1, "mkstemp");
+               if (!(wcachefp = fdopen(fd, "w")))
+                       err(1, "fdopen: '%s'", tmppath);
+               /* write last commit id (HEAD) */
+               git_oid_tostr(buf, sizeof(buf), head);
+               fprintf(wcachefp, "%s\n", buf);
+
+               writelog(fp, head);
+
+               if (rcachefp) {
+                       /* append previous log to log.html and the new cache */
+                       while (!feof(rcachefp)) {
+                               n = fread(buf, 1, sizeof(buf), rcachefp);
+                               if (ferror(rcachefp))
+                                       err(1, "fread");
+                               if (fwrite(buf, 1, n, fp) != n ||
+                                   fwrite(buf, 1, n, wcachefp) != n)
+                                       err(1, "fwrite");
+                       }
+                       fclose(rcachefp);
+               }
+               fclose(wcachefp);
+       } else {
+               if (head)
+                       writelog(fp, head);
+       }
+
+       fputs("</tbody></table>", fp);
+       writefooter(fp);
+       fclose(fp);
+
+       /* files for HEAD */
+       fp = efopen("files.html", "w");
+       writeheader(fp, "Files");
+       if (head)
+               writefiles(fp, head);
+       writefooter(fp);
+       fclose(fp);
+
+       /* summary page with branches and tags */
+       fp = efopen("refs.html", "w");
+       writeheader(fp, "Refs");
+       writerefs(fp);
+       writefooter(fp);
+       fclose(fp);
+
+       /* Atom feed */
+       fp = efopen("atom.xml", "w");
+       writeatom(fp);
+       fclose(fp);
+
+       /* rename new cache file on success */
+       if (cachefile && head) {
+               if (rename(tmppath, cachefile))
+                       err(1, "rename: '%s' to '%s'", tmppath, cachefile);
+               umask((mask = umask(0)));
+               if (chmod(cachefile,
+                   (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
+                       err(1, "chmod: '%s'", cachefile);
+       }
+
+       /* cleanup */
+       git_repository_free(repo);
+       git_libgit2_shutdown();
+
+       return 0;
+}
diff --git a/src/strlcat.c b/src/strlcat.c
new file mode 100644 (file)
index 0000000..bbfa64f
--- /dev/null
@@ -0,0 +1,57 @@
+/*     $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $    */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left).  At most dsize-1 characters
+ * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+       const char *odst = dst;
+       const char *osrc = src;
+       size_t n = dsize;
+       size_t dlen;
+
+       /* Find the end of dst and adjust bytes left but don't go past end. */
+       while (n-- != 0 && *dst != '\0')
+               dst++;
+       dlen = dst - odst;
+       n = dsize - dlen;
+
+       if (n-- == 0)
+               return(dlen + strlen(src));
+       while (*src != '\0') {
+               if (n != 0) {
+                       *dst++ = *src;
+                       n--;
+               }
+               src++;
+       }
+       *dst = '\0';
+
+       return(dlen + (src - osrc));    /* count does not include NUL */
+}
diff --git a/src/strlcpy.c b/src/strlcpy.c
new file mode 100644 (file)
index 0000000..ab420b6
--- /dev/null
@@ -0,0 +1,52 @@
+/*     $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $    */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "compat.h"
+
+/*
+ * Copy string src to buffer dst of size dsize.  At most dsize-1
+ * chars will be copied.  Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+       const char *osrc = src;
+       size_t nleft = dsize;
+
+       /* Copy as many bytes as will fit. */
+       if (nleft != 0) {
+               while (--nleft != 0) {
+                       if ((*dst++ = *src++) == '\0')
+                               break;
+               }
+       }
+
+       /* Not enough room in dst, add NUL and traverse rest of src. */
+       if (nleft == 0) {
+               if (dsize != 0)
+                       *dst = '\0';            /* NUL-terminate dst */
+               while (*src++)
+                       ;
+       }
+
+       return(src - osrc - 1); /* count does not include NUL */
+}
diff --git a/stagit-index.1 b/stagit-index.1
deleted file mode 100644 (file)
index 9abd448..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-.Dd March 2021
-.Dt STAGIT-INDEX 1
-.Os
-.Sh NAME
-.Nm stagit-index
-.Nd static git index page generator
-.Sh SYNOPSIS
-.Nm
-.Op Ar repodir...
-.Sh DESCRIPTION
-.Nm
-will create an index HTML page for the repositories specified and writes
-the HTML data to stdout.
-The repos in the index are in the same order as the arguments
-.Ar repodir
-specified.
-.Pp
-The basename of the directory is used as the repository name.
-The suffix ".git" is removed from the basename, this suffix is commonly used
-for "bare" repos.
-.Pp
-The content of the follow files specifies the meta data for each repository:
-.Bl -tag -width Ds
-.It .git/description or description (bare repos).
-description
-.It .git/owner or owner (bare repo).
-owner of repository
-.El
-.Pp
-For changing the style of the page you can use the following files:
-.Bl -tag -width Ds
-.It favicon.png
-favicon image.
-.It logo.png
-32x32 logo.
-.It style.css
-CSS stylesheet.
-.El
-.Sh SEE ALSO
-.Xr stagit 1
-.Sh AUTHORS
-.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
-.An Armaan Bhojwani Aq Mt me@armaanb.net
diff --git a/stagit-index.c b/stagit-index.c
deleted file mode 100644 (file)
index 2b243ec..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-#include <err.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <git2.h>
-
-static git_repository *repo;
-
-static const char *relpath = "";
-
-static char description[255] = "Repositories";
-static char *name = "";
-static char owner[255];
-
-void
-joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
-{
-       int r;
-
-       r = snprintf(buf, bufsiz, "%s%s%s",
-               path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
-       if (r < 0 || (size_t)r >= bufsiz)
-               errx(1, "path truncated: '%s%s%s'",
-                       path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
-}
-
-/* Escape characters below as HTML 2.0 / XML 1.0. */
-void
-xmlencode(FILE *fp, const char *s, size_t len)
-{
-       size_t i;
-
-       for (i = 0; *s && i < len; s++, i++) {
-               switch(*s) {
-               case '<':  fputs("&lt;",   fp); break;
-               case '>':  fputs("&gt;",   fp); break;
-               case '\'': fputs("&#39;" , fp); break;
-               case '&':  fputs("&amp;",  fp); break;
-               case '"':  fputs("&quot;", fp); break;
-               default:   fputc(*s, fp);
-               }
-       }
-}
-
-void
-printtimeshort(FILE *fp, const git_time *intime)
-{
-       struct tm *intm;
-       time_t t;
-       char out[32];
-
-       t = (time_t)intime->time;
-       if (!(intm = gmtime(&t)))
-               return;
-       strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
-       fputs(out, fp);
-}
-
-void
-writeheader(FILE *fp)
-{
-       fputs("<!DOCTYPE html>\n"
-               "<html>\n<head>\n"
-               "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
-               "<title>", fp);
-       xmlencode(fp, description, strlen(description));
-       fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
-       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
-       fputs("</head>\n<body>\n", fp);
-       fprintf(fp, "<table>\n<tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>\n"
-               "<td><span class=\"desc\">", relpath);
-       xmlencode(fp, description, strlen(description));
-       fputs("</span></td></tr><tr><td></td><td>\n"
-               "</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n"
-               "<table id=\"index\"><thead>\n"
-               "<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>"
-               "<td><b>Last commit</b></td></tr>"
-               "</thead><tbody>\n", fp);
-}
-
-void
-writefooter(FILE *fp)
-{
-       fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp);
-}
-
-int
-writelog(FILE *fp)
-{
-       git_commit *commit = NULL;
-       const git_signature *author;
-       git_revwalk *w = NULL;
-       git_oid id;
-       char *stripped_name = NULL, *p;
-       int ret = 0;
-
-       git_revwalk_new(&w, repo);
-       git_revwalk_push_head(w);
-       git_revwalk_simplify_first_parent(w);
-
-       if (git_revwalk_next(&id, w) ||
-           git_commit_lookup(&commit, repo, &id)) {
-               ret = -1;
-               goto err;
-       }
-
-       author = git_commit_author(commit);
-
-       /* strip .git suffix */
-       if (!(stripped_name = strdup(name)))
-               err(1, "strdup");
-       if ((p = strrchr(stripped_name, '.')))
-               if (!strcmp(p, ".git"))
-                       *p = '\0';
-
-       fputs("<tr><td><a href=\"", fp);
-       xmlencode(fp, stripped_name, strlen(stripped_name));
-       fputs("/log.html\">", fp);
-       xmlencode(fp, stripped_name, strlen(stripped_name));
-       fputs("</a></td><td>", fp);
-       xmlencode(fp, description, strlen(description));
-       fputs("</td><td>", fp);
-       xmlencode(fp, owner, strlen(owner));
-       fputs("</td><td>", fp);
-       if (author)
-               printtimeshort(fp, &(author->when));
-       fputs("</td></tr>", fp);
-
-       git_commit_free(commit);
-err:
-       git_revwalk_free(w);
-       free(stripped_name);
-
-       return ret;
-}
-
-int
-main(int argc, char *argv[])
-{
-       FILE *fp;
-       char path[PATH_MAX], repodirabs[PATH_MAX + 1];
-       const char *repodir;
-       int i, ret = 0;
-
-       if (argc < 2) {
-               fprintf(stderr, "%s [repodir...]\n", argv[0]);
-               return 1;
-       }
-
-       git_libgit2_init();
-
-#ifdef __OpenBSD__
-       for (i = 1; i < argc; i++)
-               if (unveil(argv[i], "r") == -1)
-                       err(1, "unveil: %s", argv[i]);
-
-       if (pledge("stdio rpath", NULL) == -1)
-               err(1, "pledge");
-#endif
-
-       writeheader(stdout);
-
-       for (i = 1; i < argc; i++) {
-               repodir = argv[i];
-               if (!realpath(repodir, repodirabs))
-                       err(1, "realpath");
-
-               if (git_repository_open_ext(&repo, repodir,
-                   GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
-                       fprintf(stderr, "%s: cannot open repository\n", argv[0]);
-                       ret = 1;
-                       continue;
-               }
-
-               /* use directory name as name */
-               if ((name = strrchr(repodirabs, '/')))
-                       name++;
-               else
-                       name = "";
-
-               /* read description or .git/description */
-               joinpath(path, sizeof(path), repodir, "description");
-               if (!(fp = fopen(path, "r"))) {
-                       joinpath(path, sizeof(path), repodir, ".git/description");
-                       fp = fopen(path, "r");
-               }
-               description[0] = '\0';
-               if (fp) {
-                       if (!fgets(description, sizeof(description), fp))
-                               description[0] = '\0';
-                       fclose(fp);
-               }
-
-               /* read owner or .git/owner */
-               joinpath(path, sizeof(path), repodir, "owner");
-               if (!(fp = fopen(path, "r"))) {
-                       joinpath(path, sizeof(path), repodir, ".git/owner");
-                       fp = fopen(path, "r");
-               }
-               owner[0] = '\0';
-               if (fp) {
-                       if (!fgets(owner, sizeof(owner), fp))
-                               owner[0] = '\0';
-                       owner[strcspn(owner, "\n")] = '\0';
-                       fclose(fp);
-               }
-               writelog(stdout);
-       }
-       writefooter(stdout);
-
-       /* cleanup */
-       git_repository_free(repo);
-       git_libgit2_shutdown();
-
-       return ret;
-}
diff --git a/stagit.1 b/stagit.1
deleted file mode 100644 (file)
index 23e7ca7..0000000
--- a/stagit.1
+++ /dev/null
@@ -1,108 +0,0 @@
-.Dd March 2021
-.Dt STAGIT 1
-.Os
-.Sh NAME
-.Nm stagit
-.Nd static git page generator
-.Sh SYNOPSIS
-.Nm
-.Op Fl c Ar cachefile
-.Op Fl l Ar commits
-.Ar repodir
-.Sh DESCRIPTION
-.Nm
-writes HTML pages for the repository
-.Ar repodir
-to the current directory.
-.Pp
-The options are as follows:
-.Bl -tag -width Ds
-.It Fl c Ar cachefile
-Cache the entries of the log page up to the point of
-the last commit.
-The
-.Ar cachefile
-will store the last commit id and the entries in the HTML table.
-It is up to the user to make sure the state of the
-.Ar cachefile
-is in sync with the history of the repository.
-.It Fl l Ar commits
-Write a maximum number of
-.Ar commits
-to the log.html file only.
-However the commit files are written as usual.
-.El
-.Pp
-The options
-.Fl c
-and
-.Fl l
-cannot be used at the same time.
-.Pp
-The following files will be written:
-.Bl -tag -width Ds
-.It atom.xml
-Atom XML feed
-.It files.html
-List of files in the latest tree, linking to the file.
-.It log.html
-List of commits in reverse chronological applied commit order, each commit
-links to a page with a diffstat and diff of the commit.
-.It refs.html
-Lists references of the repository such as branches and tags.
-.El
-.Pp
-For each entry in HEAD a file will be written in the format:
-file/filepath.html.
-This file will contain the textual data of the file prefixed by line numbers.
-The file will have the string "Binary file" if the data is considered to be
-non-textual.
-.Pp
-For each commit a file will be written in the format:
-commit/commitid.html.
-This file will contain the diffstat and diff of the commit.
-It will write the string "Binary files differ" if the data is considered to
-be non-textual.
-Too large diffs will be suppressed and a string
-"Diff is too large, output suppressed" will be written.
-.Pp
-When a commit HTML file exists it won't be overwritten again, note that if
-you've changed
-.Nm
-or changed one of the metadata files of the repository it is recommended to
-recreate all the output files because it will contain old data.
-To do this remove the output directory and
-.Ar cachefile ,
-then recreate the files.
-.Pp
-The basename of the directory is used as the repository name.
-The suffix ".git" is removed from the basename, this suffix is commonly used
-for "bare" repos.
-.Pp
-The content of the follow files specifies the metadata for each repository:
-.Bl -tag -width Ds
-.It .git/description or description (bare repo).
-description
-.It .git/owner or owner (bare repo).
-owner of repository
-.It .git/url or url (bare repo).
-primary clone url of the repository, for example: git://git.2f30.org/stagit
-.El
-.Pp
-When a README or LICENSE file exists in HEAD or a .gitmodules submodules file
-exists in HEAD a direct link in the menu is made.
-.Pp
-For changing the style of the page you can use the following files:
-.Bl -tag -width Ds
-.It favicon.png
-favicon image.
-.It logo.png
-32x32 logo.
-.It style.css
-CSS stylesheet.
-.El
-.Sh SEE ALSO
-.Xr stagit-index 1
-.Sh AUTHORS
-.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
-.An Armaan Bhojwani Aq Mt me@armaanb.net
diff --git a/stagit.c b/stagit.c
deleted file mode 100644 (file)
index 264604e..0000000
--- a/stagit.c
+++ /dev/null
@@ -1,1319 +0,0 @@
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <err.h>
-#include <errno.h>
-#include <libgen.h>
-#include <limits.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <git2.h>
-
-#include "compat.h"
-
-struct deltainfo {
-       git_patch *patch;
-
-       size_t addcount;
-       size_t delcount;
-};
-
-struct commitinfo {
-       const git_oid *id;
-
-       char oid[GIT_OID_HEXSZ + 1];
-       char parentoid[GIT_OID_HEXSZ + 1];
-
-       const git_signature *author;
-       const git_signature *committer;
-       const char          *summary;
-       const char          *msg;
-
-       git_diff   *diff;
-       git_commit *commit;
-       git_commit *parent;
-       git_tree   *commit_tree;
-       git_tree   *parent_tree;
-
-       size_t addcount;
-       size_t delcount;
-       size_t filecount;
-
-       struct deltainfo **deltas;
-       size_t ndeltas;
-};
-
-static git_repository *repo;
-
-static const char *relpath = "";
-static const char *repodir;
-
-static char *name = "";
-static char *strippedname = "";
-static char description[255];
-static char cloneurl[1024];
-static char *submodules;
-static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" };
-static char *license;
-static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" };
-static char *readme;
-static long long nlogcommits = -1; /* < 0 indicates not used */
-
-/* cache */
-static git_oid lastoid;
-static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
-static FILE *rcachefp, *wcachefp;
-static const char *cachefile;
-
-int cp(char fileSource[], char fileDestination[])
-{
-    int c;
-    FILE *stream_R, *stream_W; 
-
-    stream_R = fopen(fileSource, "r");
-    if (stream_R == NULL)
-        return -1;
-    stream_W = fopen(fileDestination, "w");   //create and write to file
-    if (stream_W == NULL)
-     {
-        fclose(stream_R);
-        return -2;
-     }    
-    while ((c = fgetc(stream_R)) != EOF)
-        fputc(c, stream_W);
-    fclose(stream_R);
-    fclose(stream_W);
-
-    return 0;
-}
-
-void
-joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
-{
-       int r;
-
-       r = snprintf(buf, bufsiz, "%s%s%s",
-               path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
-       if (r < 0 || (size_t)r >= bufsiz)
-               errx(1, "path truncated: '%s%s%s'",
-                       path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
-}
-
-void
-deltainfo_free(struct deltainfo *di)
-{
-       if (!di)
-               return;
-       git_patch_free(di->patch);
-       memset(di, 0, sizeof(*di));
-       free(di);
-}
-
-int
-commitinfo_getstats(struct commitinfo *ci)
-{
-       struct deltainfo *di;
-       git_diff_options opts;
-       git_diff_find_options fopts;
-       const git_diff_delta *delta;
-       const git_diff_hunk *hunk;
-       const git_diff_line *line;
-       git_patch *patch = NULL;
-       size_t ndeltas, nhunks, nhunklines;
-       size_t i, j, k;
-
-       if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
-               goto err;
-       if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
-               if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
-                       ci->parent = NULL;
-                       ci->parent_tree = NULL;
-               }
-       }
-
-       git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
-       opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
-                     GIT_DIFF_IGNORE_SUBMODULES |
-                     GIT_DIFF_INCLUDE_TYPECHANGE;
-       if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
-               goto err;
-
-       if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
-               goto err;
-       /* find renames and copies, exact matches (no heuristic) for renames. */
-       fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
-                      GIT_DIFF_FIND_EXACT_MATCH_ONLY;
-       if (git_diff_find_similar(ci->diff, &fopts))
-               goto err;
-
-       ndeltas = git_diff_num_deltas(ci->diff);
-       if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
-               err(1, "calloc");
-
-       for (i = 0; i < ndeltas; i++) {
-               if (git_patch_from_diff(&patch, ci->diff, i))
-                       goto err;
-
-               if (!(di = calloc(1, sizeof(struct deltainfo))))
-                       err(1, "calloc");
-               di->patch = patch;
-               ci->deltas[i] = di;
-
-               delta = git_patch_get_delta(patch);
-
-               /* skip stats for binary data */
-               if (delta->flags & GIT_DIFF_FLAG_BINARY)
-                       continue;
-
-               nhunks = git_patch_num_hunks(patch);
-               for (j = 0; j < nhunks; j++) {
-                       if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
-                               break;
-                       for (k = 0; ; k++) {
-                               if (git_patch_get_line_in_hunk(&line, patch, j, k))
-                                       break;
-                               if (line->old_lineno == -1) {
-                                       di->addcount++;
-                                       ci->addcount++;
-                               } else if (line->new_lineno == -1) {
-                                       di->delcount++;
-                                       ci->delcount++;
-                               }
-                       }
-               }
-       }
-       ci->ndeltas = i;
-       ci->filecount = i;
-
-       return 0;
-
-err:
-       git_diff_free(ci->diff);
-       ci->diff = NULL;
-       git_tree_free(ci->commit_tree);
-       ci->commit_tree = NULL;
-       git_tree_free(ci->parent_tree);
-       ci->parent_tree = NULL;
-       git_commit_free(ci->parent);
-       ci->parent = NULL;
-
-       if (ci->deltas)
-               for (i = 0; i < ci->ndeltas; i++)
-                       deltainfo_free(ci->deltas[i]);
-       free(ci->deltas);
-       ci->deltas = NULL;
-       ci->ndeltas = 0;
-       ci->addcount = 0;
-       ci->delcount = 0;
-       ci->filecount = 0;
-
-       return -1;
-}
-
-void
-commitinfo_free(struct commitinfo *ci)
-{
-       size_t i;
-
-       if (!ci)
-               return;
-       if (ci->deltas)
-               for (i = 0; i < ci->ndeltas; i++)
-                       deltainfo_free(ci->deltas[i]);
-
-       free(ci->deltas);
-       git_diff_free(ci->diff);
-       git_tree_free(ci->commit_tree);
-       git_tree_free(ci->parent_tree);
-       git_commit_free(ci->commit);
-       git_commit_free(ci->parent);
-       memset(ci, 0, sizeof(*ci));
-       free(ci);
-}
-
-struct commitinfo *
-commitinfo_getbyoid(const git_oid *id)
-{
-       struct commitinfo *ci;
-
-       if (!(ci = calloc(1, sizeof(struct commitinfo))))
-               err(1, "calloc");
-
-       if (git_commit_lookup(&(ci->commit), repo, id))
-               goto err;
-       ci->id = id;
-
-       git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
-       git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
-
-       ci->author = git_commit_author(ci->commit);
-       ci->committer = git_commit_committer(ci->commit);
-       ci->summary = git_commit_summary(ci->commit);
-       ci->msg = git_commit_message(ci->commit);
-
-       return ci;
-
-err:
-       commitinfo_free(ci);
-
-       return NULL;
-}
-
-FILE *
-efopen(const char *name, const char *flags)
-{
-       FILE *fp;
-
-       if (!(fp = fopen(name, flags)))
-               err(1, "fopen: '%s'", name);
-
-       return fp;
-}
-
-/* Escape characters below as HTML 2.0 / XML 1.0. */
-void
-xmlencode(FILE *fp, const char *s, size_t len)
-{
-       size_t i;
-
-       for (i = 0; *s && i < len; s++, i++) {
-               switch(*s) {
-               case '<':  fputs("&lt;",   fp); break;
-               case '>':  fputs("&gt;",   fp); break;
-               case '\'': fputs("&#39;",  fp); break;
-               case '&':  fputs("&amp;",  fp); break;
-               case '"':  fputs("&quot;", fp); break;
-               default:   fputc(*s, fp);
-               }
-       }
-}
-
-int
-mkdirp(const char *path)
-{
-       char tmp[PATH_MAX], *p;
-
-       if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
-               errx(1, "path truncated: '%s'", path);
-       for (p = tmp + (tmp[0] == '/'); *p; p++) {
-               if (*p != '/')
-                       continue;
-               *p = '\0';
-               if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
-                       return -1;
-               *p = '/';
-       }
-       if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
-               return -1;
-       return 0;
-}
-
-void
-printtimez(FILE *fp, const git_time *intime)
-{
-       struct tm *intm;
-       time_t t;
-       char out[32];
-
-       t = (time_t)intime->time;
-       if (!(intm = gmtime(&t)))
-               return;
-       strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
-       fputs(out, fp);
-}
-
-void
-printtime(FILE *fp, const git_time *intime)
-{
-       struct tm *intm;
-       time_t t;
-       char out[32];
-
-       t = (time_t)intime->time + (intime->offset * 60);
-       if (!(intm = gmtime(&t)))
-               return;
-       strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
-       if (intime->offset < 0)
-               fprintf(fp, "%s -%02d%02d", out,
-                           -(intime->offset) / 60, -(intime->offset) % 60);
-       else
-               fprintf(fp, "%s +%02d%02d", out,
-                           intime->offset / 60, intime->offset % 60);
-}
-
-void
-printtimeshort(FILE *fp, const git_time *intime)
-{
-       struct tm *intm;
-       time_t t;
-       char out[32];
-
-       t = (time_t)intime->time;
-       if (!(intm = gmtime(&t)))
-               return;
-       strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
-       fputs(out, fp);
-}
-
-void
-writeheader(FILE *fp, const char *title)
-{
-       fputs("<!DOCTYPE html>\n"
-               "<html>\n<head>\n"
-               "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
-               "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n"
-               "<title>", fp);
-       xmlencode(fp, title, strlen(title));
-       if (title[0] && strippedname[0])
-               fputs(" - ", fp);
-       xmlencode(fp, strippedname, strlen(strippedname));
-       if (description[0])
-               fputs(" - ", fp);
-       xmlencode(fp, description, strlen(description));
-       fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
-       fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
-               name, relpath);
-       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.min.css\" />\n", relpath);
-       fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%ssyntax.css\" />\n", relpath);
-       fputs("</head>\n<body>\n<table><tr><td>", fp);
-       fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
-               relpath, relpath);
-       fputs("</td><td><h1>", fp);
-       xmlencode(fp, strippedname, strlen(strippedname));
-       fputs("</h1><span class=\"desc\">", fp);
-       xmlencode(fp, description, strlen(description));
-       fputs("</span></td></tr>", fp);
-       if (cloneurl[0]) {
-               fputs("<tr class=\"url\"><td></td><td><span class=\"clone\">git clone <a href=\"", fp);
-               xmlencode(fp, cloneurl, strlen(cloneurl));
-               fputs("\">", fp);
-               xmlencode(fp, cloneurl, strlen(cloneurl));
-               fputs("</a></span></td></tr>", fp);
-       }
-       fputs("<tr><td></td><td>\n", fp);
-       fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
-       fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath);
-       fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
-       if (submodules)
-               fprintf(fp, " | <a href=\"%sfile/%s.html\">Submodules</a>",
-                       relpath, submodules);
-       if (readme)
-               fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>",
-                       relpath, readme);
-       if (license)
-               fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>",
-                       relpath, license);
-       fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp);
-}
-
-void
-writefooter(FILE *fp)
-{
-       fputs("</div>\n</body>\n</html>\n", fp);
-}
-
-int
-call_py(const char *filename, FILE *fp, const char *s, size_t len)
-{
-       // Flush HTML-file
-       fflush(fp);
-       // Copy STDOUT
-       int stdout_copy = dup(1);
-       // Redirect STDOUT
-       dup2(fileno(fp), 1);
-
-       // Python Pygments script for syntax highlighting.
-       FILE *child = popen("/usr/local/share/stagit/highlight.py", "w");
-       if (child == NULL) {
-               printf("child is null: %s", strerror(errno));
-               exit(1);
-       }
-       // Give filename through STDIN:
-       fprintf(child, "%s\n", filename);
-       // Give code to highlight through STDIN:
-       int lc;
-       size_t i;
-       for (i = 0; *s && i < len; s++, i++) {
-               if (*s == '\n') lc++;
-               fprintf(child, "%c", *s);
-       }
-
-       pclose(child);
-       fflush(stdout);
-       // Give back STDOUT.
-       dup2(stdout_copy, 1);
-       return lc;
-}
-
-int
-writeblobhtml(const char *filename, FILE *fp, const git_blob *blob)
-{
-       int lc = 0;
-       const char *s = git_blob_rawcontent(blob);
-       git_off_t len = git_blob_rawsize(blob);
-
-       if (len > 0) {
-               lc = call_py(filename, fp, s, len);
-       }
-
-       return lc;
-}
-
-void
-printcommit(FILE *fp, struct commitinfo *ci)
-{
-       fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
-               relpath, ci->oid, ci->oid);
-
-       if (ci->parentoid[0])
-               fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
-                       relpath, ci->parentoid, ci->parentoid);
-
-       if (ci->author) {
-               fputs("<b>Author:</b> ", fp);
-               xmlencode(fp, ci->author->name, strlen(ci->author->name));
-               fputs(" &lt;<a href=\"mailto:", fp);
-               xmlencode(fp, ci->author->email, strlen(ci->author->email));
-               fputs("\">", fp);
-               xmlencode(fp, ci->author->email, strlen(ci->author->email));
-               fputs("</a>&gt;\n<b>Date:</b>   ", fp);
-               printtime(fp, &(ci->author->when));
-               fputc('\n', fp);
-       }
-       if (ci->msg) {
-               fputc('\n', fp);
-               xmlencode(fp, ci->msg, strlen(ci->msg));
-               fputc('\n', fp);
-       }
-}
-
-void
-printshowfile(FILE *fp, struct commitinfo *ci)
-{
-       const git_diff_delta *delta;
-       const git_diff_hunk *hunk;
-       const git_diff_line *line;
-       git_patch *patch;
-       size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
-       char linestr[80];
-       int c;
-
-       printcommit(fp, ci);
-
-       if (!ci->deltas)
-               return;
-
-       if (ci->filecount > 1000   ||
-           ci->ndeltas   > 1000   ||
-           ci->addcount  > 100000 ||
-           ci->delcount  > 100000) {
-               fputs("Diff is too large, output suppressed.\n", fp);
-               return;
-       }
-
-       /* diff stat */
-       fputs("<b>Diffstat:</b>\n<table>", fp);
-       for (i = 0; i < ci->ndeltas; i++) {
-               delta = git_patch_get_delta(ci->deltas[i]->patch);
-
-               switch (delta->status) {
-               case GIT_DELTA_ADDED:      c = 'A'; break;
-               case GIT_DELTA_COPIED:     c = 'C'; break;
-               case GIT_DELTA_DELETED:    c = 'D'; break;
-               case GIT_DELTA_MODIFIED:   c = 'M'; break;
-               case GIT_DELTA_RENAMED:    c = 'R'; break;
-               case GIT_DELTA_TYPECHANGE: c = 'T'; break;
-               default:                   c = ' '; break;
-               }
-               if (c == ' ')
-                       fprintf(fp, "<tr><td>%c", c);
-               else
-                       fprintf(fp, "<tr><td class=\"%c\">%c", c, c);
-
-               fprintf(fp, "</td><td><a href=\"#h%zu\">", i);
-               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
-               if (strcmp(delta->old_file.path, delta->new_file.path)) {
-                       fputs(" -&gt; ", fp);
-                       xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
-               }
-
-               add = ci->deltas[i]->addcount;
-               del = ci->deltas[i]->delcount;
-               changed = add + del;
-               total = sizeof(linestr) - 2;
-               if (changed > total) {
-                       if (add)
-                               add = ((float)total / changed * add) + 1;
-                       if (del)
-                               del = ((float)total / changed * del) + 1;
-               }
-               memset(&linestr, '+', add);
-               memset(&linestr[add], '-', del);
-
-               fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">",
-                       ci->deltas[i]->addcount + ci->deltas[i]->delcount);
-               fwrite(&linestr, 1, add, fp);
-               fputs("</span><span class=\"d\">", fp);
-               fwrite(&linestr[add], 1, del, fp);
-               fputs("</span></td></tr>\n", fp);
-       }
-       fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
-               ci->filecount, ci->filecount == 1 ? "" : "s",
-               ci->addcount,  ci->addcount  == 1 ? "" : "s",
-               ci->delcount,  ci->delcount  == 1 ? "" : "s");
-
-       fputs("<hr/>", fp);
-
-       for (i = 0; i < ci->ndeltas; i++) {
-               patch = ci->deltas[i]->patch;
-               delta = git_patch_get_delta(patch);
-               fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath);
-               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
-               fputs(".html\">", fp);
-               xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
-               fprintf(fp, "</a> b/<a href=\"%sfile/", relpath);
-               xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
-               fprintf(fp, ".html\">");
-               xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
-               fprintf(fp, "</a></b>\n");
-
-               /* check binary data */
-               if (delta->flags & GIT_DIFF_FLAG_BINARY) {
-                       fputs("Binary files differ.\n", fp);
-                       continue;
-               }
-
-               nhunks = git_patch_num_hunks(patch);
-               for (j = 0; j < nhunks; j++) {
-                       if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
-                               break;
-
-                       fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j);
-                       xmlencode(fp, hunk->header, hunk->header_len);
-                       fputs("</a>", fp);
-
-                       for (k = 0; ; k++) {
-                               if (git_patch_get_line_in_hunk(&line, patch, j, k))
-                                       break;
-                               if (line->old_lineno == -1)
-                                       fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
-                                               i, j, k, i, j, k);
-                               else if (line->new_lineno == -1)
-                                       fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
-                                               i, j, k, i, j, k);
-                               else
-                                       fputc(' ', fp);
-                               xmlencode(fp, line->content, line->content_len);
-                               if (line->old_lineno == -1 || line->new_lineno == -1)
-                                       fputs("</a>", fp);
-                       }
-               }
-       }
-}
-
-void
-writelogline(FILE *fp, struct commitinfo *ci)
-{
-       fputs("<tr><td>", fp);
-       if (ci->author)
-               printtimeshort(fp, &(ci->author->when));
-       fputs("</td><td>", fp);
-       if (ci->summary) {
-               fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
-               xmlencode(fp, ci->summary, strlen(ci->summary));
-               fputs("</a>", fp);
-       }
-       fputs("</td><td>", fp);
-       if (ci->author)
-               xmlencode(fp, ci->author->name, strlen(ci->author->name));
-       fputs("</td><td class=\"num\" align=\"right\">", fp);
-       fprintf(fp, "%zu", ci->filecount);
-       fputs("</td><td class=\"num\" align=\"right\">", fp);
-       fprintf(fp, "+%zu", ci->addcount);
-       fputs("</td><td class=\"num\" align=\"right\">", fp);
-       fprintf(fp, "-%zu", ci->delcount);
-       fputs("</td></tr>\n", fp);
-}
-
-int
-writelog(FILE *fp, const git_oid *oid)
-{
-       struct commitinfo *ci;
-       git_revwalk *w = NULL;
-       git_oid id;
-       char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
-       FILE *fpfile;
-       int r;
-
-       git_revwalk_new(&w, repo);
-       git_revwalk_push(w, oid);
-       git_revwalk_simplify_first_parent(w);
-
-       while (!git_revwalk_next(&id, w)) {
-               relpath = "";
-
-               if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
-                       break;
-
-               git_oid_tostr(oidstr, sizeof(oidstr), &id);
-               r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
-               if (r < 0 || (size_t)r >= sizeof(path))
-                       errx(1, "path truncated: 'commit/%s.html'", oidstr);
-               r = access(path, F_OK);
-
-               /* optimization: if there are no log lines to write and
-                  the commit file already exists: skip the diffstat */
-               if (!nlogcommits && !r)
-                       continue;
-
-               if (!(ci = commitinfo_getbyoid(&id)))
-                       break;
-               /* diffstat: for stagit HTML required for the log.html line */
-               if (commitinfo_getstats(ci) == -1)
-                       goto err;
-
-               if (nlogcommits < 0) {
-                       writelogline(fp, ci);
-               } else if (nlogcommits > 0) {
-                       writelogline(fp, ci);
-                       nlogcommits--;
-                       if (!nlogcommits && ci->parentoid[0])
-                               fputs("<tr><td></td><td colspan=\"5\">"
-                                     "More commits remaining [...]</td>"
-                                     "</tr>\n", fp);
-               }
-
-               if (cachefile)
-                       writelogline(wcachefp, ci);
-
-               /* check if file exists if so skip it */
-               if (r) {
-                       relpath = "../";
-                       fpfile = efopen(path, "w");
-                       writeheader(fpfile, ci->summary);
-                       fputs("<pre>", fpfile);
-                       printshowfile(fpfile, ci);
-                       fputs("</pre>\n", fpfile);
-                       writefooter(fpfile);
-                       fclose(fpfile);
-               }
-err:
-               commitinfo_free(ci);
-       }
-       git_revwalk_free(w);
-
-       relpath = "";
-
-       return 0;
-}
-
-void
-printcommitatom(FILE *fp, struct commitinfo *ci)
-{
-       fputs("<entry>\n", fp);
-
-       fprintf(fp, "<id>%s</id>\n", ci->oid);
-       if (ci->author) {
-               fputs("<published>", fp);
-               printtimez(fp, &(ci->author->when));
-               fputs("</published>\n", fp);
-       }
-       if (ci->committer) {
-               fputs("<updated>", fp);
-               printtimez(fp, &(ci->committer->when));
-               fputs("</updated>\n", fp);
-       }
-       if (ci->summary) {
-               fputs("<title type=\"text\">", fp);
-               xmlencode(fp, ci->summary, strlen(ci->summary));
-               fputs("</title>\n", fp);
-       }
-       fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.html\" />\n",
-               ci->oid);
-
-       if (ci->author) {
-               fputs("<author>\n<name>", fp);
-               xmlencode(fp, ci->author->name, strlen(ci->author->name));
-               fputs("</name>\n<email>", fp);
-               xmlencode(fp, ci->author->email, strlen(ci->author->email));
-               fputs("</email>\n</author>\n", fp);
-       }
-
-       fputs("<content type=\"text\">", fp);
-       fprintf(fp, "commit %s\n", ci->oid);
-       if (ci->parentoid[0])
-               fprintf(fp, "parent %s\n", ci->parentoid);
-       if (ci->author) {
-               fputs("Author: ", fp);
-               xmlencode(fp, ci->author->name, strlen(ci->author->name));
-               fputs(" &lt;", fp);
-               xmlencode(fp, ci->author->email, strlen(ci->author->email));
-               fputs("&gt;\nDate:   ", fp);
-               printtime(fp, &(ci->author->when));
-               fputc('\n', fp);
-       }
-       if (ci->msg) {
-               fputc('\n', fp);
-               xmlencode(fp, ci->msg, strlen(ci->msg));
-       }
-       fputs("\n</content>\n</entry>\n", fp);
-}
-
-int
-writeatom(FILE *fp)
-{
-       struct commitinfo *ci;
-       git_revwalk *w = NULL;
-       git_oid id;
-       size_t i, m = 100; /* last 'm' commits */
-
-       fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-             "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
-       xmlencode(fp, strippedname, strlen(strippedname));
-       fputs(", branch HEAD</title>\n<subtitle>", fp);
-       xmlencode(fp, description, strlen(description));
-       fputs("</subtitle>\n", fp);
-
-       git_revwalk_new(&w, repo);
-       git_revwalk_push_head(w);
-       git_revwalk_simplify_first_parent(w);
-
-       for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
-               if (!(ci = commitinfo_getbyoid(&id)))
-                       break;
-               printcommitatom(fp, ci);
-               commitinfo_free(ci);
-       }
-       git_revwalk_free(w);
-
-       fputs("</feed>\n", fp);
-
-       return 0;
-}
-
-int
-writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t filesize)
-{
-       char tmp[PATH_MAX] = "", *d;
-       const char *p;
-       int lc = 0;
-       FILE *fp;
-
-       if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
-               errx(1, "path truncated: '%s'", fpath);
-       if (!(d = dirname(tmp)))
-               err(1, "dirname");
-       if (mkdirp(d))
-               return -1;
-
-       for (p = fpath, tmp[0] = '\0'; *p; p++) {
-               if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp))
-                       errx(1, "path truncated: '../%s'", tmp);
-       }
-       relpath = tmp;
-
-       fp = efopen(fpath, "w");
-       writeheader(fp, filename);
-       fputs("<p> ", fp);
-       xmlencode(fp, filename, strlen(filename));
-       fprintf(fp, " (%juB)", (uintmax_t)filesize);
-       fputs("</p><hr/>", fp);
-
-       if (git_blob_is_binary((git_blob *)obj)) {
-               fputs("<p>Binary file.</p>\n", fp);
-       } else {
-               lc = writeblobhtml(filename, fp, (git_blob *)obj);
-               if (ferror(fp))
-                       err(1, "fwrite");
-       }
-       writefooter(fp);
-       fclose(fp);
-
-       relpath = "";
-
-       return lc;
-}
-
-const char *
-filemode(git_filemode_t m)
-{
-       static char mode[11];
-
-       memset(mode, '-', sizeof(mode) - 1);
-       mode[10] = '\0';
-
-       if (S_ISREG(m))
-               mode[0] = '-';
-       else if (S_ISBLK(m))
-               mode[0] = 'b';
-       else if (S_ISCHR(m))
-               mode[0] = 'c';
-       else if (S_ISDIR(m))
-               mode[0] = 'd';
-       else if (S_ISFIFO(m))
-               mode[0] = 'p';
-       else if (S_ISLNK(m))
-               mode[0] = 'l';
-       else if (S_ISSOCK(m))
-               mode[0] = 's';
-       else
-               mode[0] = '?';
-
-       if (m & S_IRUSR) mode[1] = 'r';
-       if (m & S_IWUSR) mode[2] = 'w';
-       if (m & S_IXUSR) mode[3] = 'x';
-       if (m & S_IRGRP) mode[4] = 'r';
-       if (m & S_IWGRP) mode[5] = 'w';
-       if (m & S_IXGRP) mode[6] = 'x';
-       if (m & S_IROTH) mode[7] = 'r';
-       if (m & S_IWOTH) mode[8] = 'w';
-       if (m & S_IXOTH) mode[9] = 'x';
-
-       if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
-       if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
-       if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
-
-       return mode;
-}
-
-int
-writefilestree(FILE *fp, git_tree *tree, const char *path)
-{
-       const git_tree_entry *entry = NULL;
-       git_submodule *module = NULL;
-       git_object *obj = NULL;
-       git_off_t filesize;
-       const char *entryname;
-       char filepath[PATH_MAX], entrypath[PATH_MAX];
-       size_t count, i;
-       int lc, r, ret;
-
-       count = git_tree_entrycount(tree);
-       for (i = 0; i < count; i++) {
-               if (!(entry = git_tree_entry_byindex(tree, i)) ||
-                   !(entryname = git_tree_entry_name(entry)))
-                       return -1;
-               joinpath(entrypath, sizeof(entrypath), path, entryname);
-
-               r = snprintf(filepath, sizeof(filepath), "file/%s.html",
-                        entrypath);
-               if (r < 0 || (size_t)r >= sizeof(filepath))
-                       errx(1, "path truncated: 'file/%s.html'", entrypath);
-
-               if (!git_tree_entry_to_object(&obj, repo, entry)) {
-                       switch (git_object_type(obj)) {
-                       case GIT_OBJ_BLOB:
-                               break;
-                       case GIT_OBJ_TREE:
-                               /* NOTE: recurses */
-                               ret = writefilestree(fp, (git_tree *)obj,
-                                                    entrypath);
-                               git_object_free(obj);
-                               if (ret)
-                                       return ret;
-                               continue;
-                       default:
-                               git_object_free(obj);
-                               continue;
-                       }
-
-                       filesize = git_blob_rawsize((git_blob *)obj);
-                       lc = writeblob(obj, filepath, entryname, filesize);
-
-                       fputs("<tr><td>", fp);
-                       fputs(filemode(git_tree_entry_filemode(entry)), fp);
-                       fprintf(fp, "</td><td><a href=\"%s", relpath);
-                       xmlencode(fp, filepath, strlen(filepath));
-                       fputs("\">", fp);
-                       xmlencode(fp, entrypath, strlen(entrypath));
-                       fputs("</a></td><td class=\"num\" align=\"right\">", fp);
-                       if (lc > 0)
-                               fprintf(fp, "%dL", lc);
-                       else
-                               fprintf(fp, "%juB", (uintmax_t)filesize);
-                       fputs("</td></tr>\n", fp);
-                       git_object_free(obj);
-               } else if (!git_submodule_lookup(&module, repo, entryname)) {
-                       fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">",
-                               relpath);
-                       xmlencode(fp, entrypath, strlen(entrypath));
-                       git_submodule_free(module);
-                       fputs("</a></td><td class=\"num\" align=\"right\"></td></tr>\n", fp);
-               }
-       }
-
-       return 0;
-}
-
-int
-writefiles(FILE *fp, const git_oid *id)
-{
-       git_tree *tree = NULL;
-       git_commit *commit = NULL;
-       int ret = -1;
-
-       fputs("<table id=\"files\"><thead>\n<tr>"
-             "<td><b>Mode</b></td><td><b>Name</b></td>"
-             "<td class=\"num\" align=\"right\"><b>Size</b></td>"
-             "</tr>\n</thead><tbody>\n", fp);
-
-       if (!git_commit_lookup(&commit, repo, id) &&
-           !git_commit_tree(&tree, commit))
-               ret = writefilestree(fp, tree, "");
-
-       fputs("</tbody></table>", fp);
-
-       git_commit_free(commit);
-       git_tree_free(tree);
-
-       return ret;
-}
-
-int
-refs_cmp(const void *v1, const void *v2)
-{
-       git_reference *r1 = (*(git_reference **)v1);
-       git_reference *r2 = (*(git_reference **)v2);
-       int r;
-
-       if ((r = git_reference_is_branch(r1) - git_reference_is_branch(r2)))
-               return r;
-
-       return strcmp(git_reference_shorthand(r1),
-                     git_reference_shorthand(r2));
-}
-
-int
-writerefs(FILE *fp)
-{
-       struct commitinfo *ci;
-       const git_oid *id = NULL;
-       git_object *obj = NULL;
-       git_reference *dref = NULL, *r, *ref = NULL;
-       git_reference_iterator *it = NULL;
-       git_reference **refs = NULL;
-       size_t count, i, j, refcount;
-       const char *titles[] = { "Branches", "Tags" };
-       const char *ids[] = { "branches", "tags" };
-       const char *name;
-
-       if (git_reference_iterator_new(&it, repo))
-               return -1;
-
-       for (refcount = 0; !git_reference_next(&ref, it); refcount++) {
-               if (!(refs = reallocarray(refs, refcount + 1, sizeof(git_reference *))))
-                       err(1, "realloc");
-               refs[refcount] = ref;
-       }
-       git_reference_iterator_free(it);
-
-       /* sort by type then shorthand name */
-       qsort(refs, refcount, sizeof(git_reference *), refs_cmp);
-
-       for (j = 0; j < 2; j++) {
-               for (i = 0, count = 0; i < refcount; i++) {
-                       if (!(git_reference_is_branch(refs[i]) && j == 0) &&
-                           !(git_reference_is_tag(refs[i]) && j == 1))
-                               continue;
-
-                       switch (git_reference_type(refs[i])) {
-                       case GIT_REF_SYMBOLIC:
-                               if (git_reference_resolve(&dref, refs[i]))
-                                       goto err;
-                               r = dref;
-                               break;
-                       case GIT_REF_OID:
-                               r = refs[i];
-                               break;
-                       default:
-                               continue;
-                       }
-                       if (!git_reference_target(r) ||
-                           git_reference_peel(&obj, r, GIT_OBJ_ANY))
-                               goto err;
-                       if (!(id = git_object_id(obj)))
-                               goto err;
-                       if (!(ci = commitinfo_getbyoid(id)))
-                               break;
-
-                       /* print header if it has an entry (first). */
-                       if (++count == 1) {
-                               fprintf(fp, "<h2>%s</h2><table id=\"%s\">"
-                                       "<thead>\n<tr><td><b>Name</b></td>"
-                                       "<td><b>Last commit date</b></td>"
-                                       "<td><b>Author</b></td>\n</tr>\n"
-                                       "</thead><tbody>\n",
-                                        titles[j], ids[j]);
-                       }
-
-                       relpath = "";
-                       name = git_reference_shorthand(r);
-
-                       fputs("<tr><td>", fp);
-                       xmlencode(fp, name, strlen(name));
-                       fputs("</td><td>", fp);
-                       if (ci->author)
-                               printtimeshort(fp, &(ci->author->when));
-                       fputs("</td><td>", fp);
-                       if (ci->author)
-                               xmlencode(fp, ci->author->name, strlen(ci->author->name));
-                       fputs("</td></tr>\n", fp);
-
-                       relpath = "../";
-
-                       commitinfo_free(ci);
-                       git_object_free(obj);
-                       obj = NULL;
-                       git_reference_free(dref);
-                       dref = NULL;
-               }
-               /* table footer */
-               if (count)
-                       fputs("</tbody></table><br/>", fp);
-       }
-
-err:
-       git_object_free(obj);
-       git_reference_free(dref);
-
-       for (i = 0; i < refcount; i++)
-               git_reference_free(refs[i]);
-       free(refs);
-
-       return 0;
-}
-
-void
-usage(char *argv0)
-{
-       fprintf(stderr, "%s [-c cachefile | -l commits] repodir\n", argv0);
-       exit(1);
-}
-
-int
-main(int argc, char *argv[])
-{
-       git_object *obj = NULL;
-       const git_oid *head = NULL;
-       mode_t mask;
-       FILE *fp, *fpread;
-       char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
-       char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
-       size_t n;
-       int i, fd;
-
-       for (i = 1; i < argc; i++) {
-               if (argv[i][0] != '-') {
-                       if (repodir)
-                               usage(argv[0]);
-                       repodir = argv[i];
-               } else if (argv[i][1] == 'c') {
-                       if (nlogcommits > 0 || i + 1 >= argc)
-                               usage(argv[0]);
-                       cachefile = argv[++i];
-               } else if (argv[i][1] == 'l') {
-                       if (cachefile || i + 1 >= argc)
-                               usage(argv[0]);
-                       errno = 0;
-                       nlogcommits = strtoll(argv[++i], &p, 10);
-                       if (argv[i][0] == '\0' || *p != '\0' ||
-                           nlogcommits <= 0 || errno)
-                               usage(argv[0]);
-               }
-       }
-       if (!repodir)
-               usage(argv[0]);
-
-       if (!realpath(repodir, repodirabs))
-               err(1, "realpath");
-
-       git_libgit2_init();
-
-#ifdef __OpenBSD__
-       if (unveil(repodir, "r") == -1)
-               err(1, "unveil: %s", repodir);
-       if (unveil(".", "rwc") == -1)
-               err(1, "unveil: .");
-       if (cachefile && unveil(cachefile, "rwc") == -1)
-               err(1, "unveil: %s", cachefile);
-
-       if (cachefile) {
-               if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
-                       err(1, "pledge");
-       } else {
-               if (pledge("stdio rpath wpath cpath", NULL) == -1)
-                       err(1, "pledge");
-       }
-#endif
-
-       if (git_repository_open_ext(&repo, repodir,
-               GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
-               fprintf(stderr, "%s: cannot open repository\n", argv[0]);
-               return 1;
-       }
-
-       /* find HEAD */
-       if (!git_revparse_single(&obj, repo, "HEAD"))
-               head = git_object_id(obj);
-       git_object_free(obj);
-
-       /* use directory name as name */
-       if ((name = strrchr(repodirabs, '/')))
-               name++;
-       else
-               name = "";
-
-       /* copy css */
-       char cwd[PATH_MAX];
-       strcpy(cwd, getcwd(cwd, sizeof(cwd)));
-       cp("/usr/local/share/stagit/syntax.css", strcat(cwd, "/syntax.css"));
-       strcpy(cwd, getcwd(cwd, sizeof(cwd)));
-       cp("/usr/local/share/stagit/style.min.css", strcat(cwd, "/style.min.css"));
-
-       /* strip .git suffix */
-       if (!(strippedname = strdup(name)))
-               err(1, "strdup");
-       if ((p = strrchr(strippedname, '.')))
-               if (!strcmp(p, ".git"))
-                       *p = '\0';
-
-       /* read description or .git/description */
-       joinpath(path, sizeof(path), repodir, "description");
-       if (!(fpread = fopen(path, "r"))) {
-               joinpath(path, sizeof(path), repodir, ".git/description");
-               fpread = fopen(path, "r");
-       }
-       if (fpread) {
-               if (!fgets(description, sizeof(description), fpread))
-                       description[0] = '\0';
-               fclose(fpread);
-       }
-
-       /* read url or .git/url */
-       joinpath(path, sizeof(path), repodir, "url");
-       if (!(fpread = fopen(path, "r"))) {
-               joinpath(path, sizeof(path), repodir, ".git/url");
-               fpread = fopen(path, "r");
-       }
-       if (fpread) {
-               if (!fgets(cloneurl, sizeof(cloneurl), fpread))
-                       cloneurl[0] = '\0';
-               cloneurl[strcspn(cloneurl, "\n")] = '\0';
-               fclose(fpread);
-       }
-
-       /* check LICENSE */
-       for (i = 0; i < sizeof(licensefiles) / sizeof(*licensefiles) && !license; i++) {
-               if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
-                   git_object_type(obj) == GIT_OBJ_BLOB)
-                       license = licensefiles[i] + strlen("HEAD:");
-               git_object_free(obj);
-       }
-
-       /* check README */
-       for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles) && !readme; i++) {
-               if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
-                   git_object_type(obj) == GIT_OBJ_BLOB)
-                       readme = readmefiles[i] + strlen("HEAD:");
-               git_object_free(obj);
-       }
-
-       if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
-           git_object_type(obj) == GIT_OBJ_BLOB)
-               submodules = ".gitmodules";
-       git_object_free(obj);
-
-       /* log for HEAD */
-       fp = efopen("log.html", "w");
-       relpath = "";
-       mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
-       writeheader(fp, "Log");
-       fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
-             "<td><b>Commit</b></td>"
-             "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>"
-             "<td class=\"num\" align=\"right\"><b>+</b></td>"
-             "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
-
-       if (cachefile && head) {
-               /* read from cache file (does not need to exist) */
-               if ((rcachefp = fopen(cachefile, "r"))) {
-                       if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
-                               errx(1, "%s: no object id", cachefile);
-                       if (git_oid_fromstr(&lastoid, lastoidstr))
-                               errx(1, "%s: invalid object id", cachefile);
-               }
-
-               /* write log to (temporary) cache */
-               if ((fd = mkstemp(tmppath)) == -1)
-                       err(1, "mkstemp");
-               if (!(wcachefp = fdopen(fd, "w")))
-                       err(1, "fdopen: '%s'", tmppath);
-               /* write last commit id (HEAD) */
-               git_oid_tostr(buf, sizeof(buf), head);
-               fprintf(wcachefp, "%s\n", buf);
-
-               writelog(fp, head);
-
-               if (rcachefp) {
-                       /* append previous log to log.html and the new cache */
-                       while (!feof(rcachefp)) {
-                               n = fread(buf, 1, sizeof(buf), rcachefp);
-                               if (ferror(rcachefp))
-                                       err(1, "fread");
-                               if (fwrite(buf, 1, n, fp) != n ||
-                                   fwrite(buf, 1, n, wcachefp) != n)
-                                       err(1, "fwrite");
-                       }
-                       fclose(rcachefp);
-               }
-               fclose(wcachefp);
-       } else {
-               if (head)
-                       writelog(fp, head);
-       }
-
-       fputs("</tbody></table>", fp);
-       writefooter(fp);
-       fclose(fp);
-
-       /* files for HEAD */
-       fp = efopen("files.html", "w");
-       writeheader(fp, "Files");
-       if (head)
-               writefiles(fp, head);
-       writefooter(fp);
-       fclose(fp);
-
-       /* summary page with branches and tags */
-       fp = efopen("refs.html", "w");
-       writeheader(fp, "Refs");
-       writerefs(fp);
-       writefooter(fp);
-       fclose(fp);
-
-       /* Atom feed */
-       fp = efopen("atom.xml", "w");
-       writeatom(fp);
-       fclose(fp);
-
-       /* rename new cache file on success */
-       if (cachefile && head) {
-               if (rename(tmppath, cachefile))
-                       err(1, "rename: '%s' to '%s'", tmppath, cachefile);
-               umask((mask = umask(0)));
-               if (chmod(cachefile,
-                   (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
-                       err(1, "chmod: '%s'", cachefile);
-       }
-
-       /* cleanup */
-       git_repository_free(repo);
-       git_libgit2_shutdown();
-
-       return 0;
-}
diff --git a/strlcat.c b/strlcat.c
deleted file mode 100644 (file)
index bbfa64f..0000000
--- a/strlcat.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/*     $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $    */
-
-/*
- * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <sys/types.h>
-#include <string.h>
-
-#include "compat.h"
-
-/*
- * Appends src to string dst of size dsize (unlike strncat, dsize is the
- * full size of dst, not space left).  At most dsize-1 characters
- * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).
- * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
- * If retval >= dsize, truncation occurred.
- */
-size_t
-strlcat(char *dst, const char *src, size_t dsize)
-{
-       const char *odst = dst;
-       const char *osrc = src;
-       size_t n = dsize;
-       size_t dlen;
-
-       /* Find the end of dst and adjust bytes left but don't go past end. */
-       while (n-- != 0 && *dst != '\0')
-               dst++;
-       dlen = dst - odst;
-       n = dsize - dlen;
-
-       if (n-- == 0)
-               return(dlen + strlen(src));
-       while (*src != '\0') {
-               if (n != 0) {
-                       *dst++ = *src;
-                       n--;
-               }
-               src++;
-       }
-       *dst = '\0';
-
-       return(dlen + (src - osrc));    /* count does not include NUL */
-}
diff --git a/strlcpy.c b/strlcpy.c
deleted file mode 100644 (file)
index ab420b6..0000000
--- a/strlcpy.c
+++ /dev/null
@@ -1,52 +0,0 @@
-/*     $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $    */
-
-/*
- * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <sys/types.h>
-#include <string.h>
-
-#include "compat.h"
-
-/*
- * Copy string src to buffer dst of size dsize.  At most dsize-1
- * chars will be copied.  Always NUL terminates (unless dsize == 0).
- * Returns strlen(src); if retval >= dsize, truncation occurred.
- */
-size_t
-strlcpy(char *dst, const char *src, size_t dsize)
-{
-       const char *osrc = src;
-       size_t nleft = dsize;
-
-       /* Copy as many bytes as will fit. */
-       if (nleft != 0) {
-               while (--nleft != 0) {
-                       if ((*dst++ = *src++) == '\0')
-                               break;
-               }
-       }
-
-       /* Not enough room in dst, add NUL and traverse rest of src. */
-       if (nleft == 0) {
-               if (dsize != 0)
-                       *dst = '\0';            /* NUL-terminate dst */
-               while (*src++)
-                       ;
-       }
-
-       return(src - osrc - 1); /* count does not include NUL */
-}
diff --git a/style.css b/style.css
deleted file mode 100644 (file)
index af0d598..0000000
--- a/style.css
+++ /dev/null
@@ -1,211 +0,0 @@
-body {
-       font-size: 0.8rem;
-       font-family: monospace;
-       color: #ebdbb2;
-       background-color: #282828;
-       max-width: max-content;
-       min-width: min-content;
-}
-
-pre {
-       tab-size: 4;
-}
-
-h1, h2, h3, h4, h5, h6 {
-       margin: 0;
-}
-
-img, h1, h2 {
-       vertical-align: middle;
-}
-
-img {
-       border: 0;
-}
-
-a:target {
-       background-color: #eee;
-}
-
-a.d,
-a.h,
-a.i,
-a.line {
-       text-decoration: none;
-}
-
-#blob {
-       display: block;
-       max-width: 100%;
-}
-
-article.markup {
-       font-size: 15px;
-       border: 2px solid #eee;
-       border-radius: 10px;
-       font-family: sans-serif;
-       padding: 2.5em;
-       margin: 2em 0;
-}
-
-article.markup code {
-       font-size: 0.9em;
-       border: 1px solid #dbdbdb;
-       background-color: #f7f7f7;
-       padding: 0 0.3em;
-       border-radius: 0.3em
-}
-
-article.markup pre code {
-       border: none;
-       background: none;
-       padding: 0;
-       border-radius: 0;
-}
-
-article.markup pre {
-       background-color: #f7f7f7;
-       padding: 1em;
-       border: 1px solid #dbdbdb;
-       border-radius: 0.3em;
-}
-
-article.markup h1 {
-       font-size: 2.4em;
-       padding-bottom: 6px;
-       border-bottom: 5px solid #0000000a;
-}
-
-article.markup h2 {
-       font-size: 1.9em;
-       padding-bottom: 5px;
-       border-bottom: 2px solid #00000014;
-}
-
-article.markup h3 {
-       font-size: 1.5em;
-}
-
-article.markup h4 {
-       font-size: 1.3em;
-}
-
-article.markup h5 {
-       font-size: 1.1em;
-}
-
-article.markup h6 {
-       font-size: 1em;
-}
-
-article img {
-       max-width: 100%;
-}
-
-.linenos {
-       margin-right: 0;
-       border-right: 1px solid rgb(0 0 0 / 8%);
-       user-select: none;
-}
-
-.linenos a {
-       margin-right: 0.9em;
-       user-select: none;
-       text-decoration: none;
-}
-
-#blob a {
-       color: #777;
-}
-
-#blob a:hover {
-       color: #eee;
-       text-decoration: none;
-}
-
-a {
-       color: #83a598;
-}
-
-table thead td {
-       font-weight: bold;
-}
-
-table td {
-       padding: 0 0.4em;
-}
-
-#content table td {
-       vertical-align: top;
-       white-space: nowrap;
-}
-
-#branches tr:hover td,
-#tags tr:hover td,
-#index tr:hover td,
-#log tr:hover td,
-#files tr:hover td {
-       background-color: #333;
-}
-
-#index tr td:nth-child(2),
-#tags tr td:nth-child(3),
-#branches tr td:nth-child(3),
-#log tr td:nth-child(2) {
-       white-space: normal;
-}
-
-td.num {
-       text-align: right;
-}
-
-.desc {
-       color: #928374;
-}
-
-hr {
-       border: 0;
-       border-top: 1px solid #928374;
-       height: 1px;
-}
-
-pre {
-       font-family: monospace;
-}
-
-pre a.h {
-       color: #fe8019;
-}
-
-.A,
-span.i,
-pre a.i {
-       color: #b8bb26;
-}
-
-.D,
-span.d,
-pre a.d {
-       color: #fb4934;
-}
-
-pre a.h:hover,
-pre a.i:hover,
-pre a.d:hover {
-       text-decoration: none;
-}
-
-.url td:nth-child(2) {
-       padding-top:    0.2em;
-       padding-bottom: 0.9em;
-}
-
-.url td:nth-child(2) span {
-       padding: 1px 5px;
-       border: 1px solid #ebdbb2;
-       border-radius: 5px;
-}
-
-.url td:nth-child(2) span a {
-       color: #ebdbb2;
-}
diff --git a/style.min.css b/style.min.css
deleted file mode 100644 (file)
index 063054a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-body{font-size:.8rem;font-family:monospace;color:#ebdbb2;background-color:#282828;max-width:max-content;min-width:min-content}pre{tab-size:4}h1,h2,h3,h4,h5,h6{margin:0}img,h1,h2{vertical-align:middle}img{border:0}a:target{background-color:#eee}a.d,a.h,a.i,a.line{text-decoration:none}#blob{display:block;max-width:100%}article.markup{font-size:15px;border:2px solid #eee;border-radius:10px;font-family:sans-serif;padding:2.5em;margin:2em 0}article.markup code{font-size:.9em;border:1px solid #dbdbdb;background-color:#f7f7f7;padding:0 .3em;border-radius:.3em}article.markup pre code{border:none;background:0 0;padding:0;border-radius:0}article.markup pre{background-color:#f7f7f7;padding:1em;border:1px solid #dbdbdb;border-radius:.3em}article.markup h1{font-size:2.4em;padding-bottom:6px;border-bottom:5px solid #0000000a}article.markup h2{font-size:1.9em;padding-bottom:5px;border-bottom:2px solid #00000014}article.markup h3{font-size:1.5em}article.markup h4{font-size:1.3em}article.markup h5{font-size:1.1em}article.markup h6{font-size:1em}article img{max-width:100%}.linenos{margin-right:0;border-right:1px solid rgb(0 0 0/8%);user-select:none}.linenos a{margin-right:.9em;user-select:none;text-decoration:none}#blob a{color:#777}#blob a:hover{color:#eee;text-decoration:none}a{color:#83a598}table thead td{font-weight:700}table td{padding:0 .4em}#content table td{vertical-align:top;white-space:nowrap}#branches tr:hover td,#tags tr:hover td,#index tr:hover td,#log tr:hover td,#files tr:hover td{background-color:#333}#index tr td:nth-child(2),#tags tr td:nth-child(3),#branches tr td:nth-child(3),#log tr td:nth-child(2){white-space:normal}td.num{text-align:right}.desc{color:#928374}hr{border:0;border-top:1px solid #928374;height:1px}pre{font-family:monospace}pre a.h{color:#fe8019}.A,span.i,pre a.i{color:#b8bb26}.D,span.d,pre a.d{color:#fb4934}pre a.h:hover,pre a.i:hover,pre a.d:hover{text-decoration:none}.url td:nth-child(2){padding-top:.2em;padding-bottom:.9em}.url td:nth-child(2) span{padding:1px 5px;border:1px solid #ebdbb2;border-radius:5px}.url td:nth-child(2) span a{color:#ebdbb2}
\ No newline at end of file
diff --git a/syntax.css b/syntax.css
deleted file mode 100644 (file)
index 4c774f6..0000000
+++ /dev/null
@@ -1 +0,0 @@
-.highlight .hll{background-color:#ffc}.highlight{background:#282828;color:#ebdbb2;background-color:#282828}.highlight .c{color:#928374;font-style:italic;background-color:#282828}.highlight .err{color:#ebdbb2;background-color:#282828}.highlight .esc{color:#ebdbb2;background-color:#282828}.highlight .g{color:#ebdbb2;background-color:#282828}.highlight .k{color:#fe8019;background-color:#282828}.highlight .l{color:#ebdbb2;background-color:#282828}.highlight .n{color:#ebdbb2;background-color:#282828}.highlight .o{color:#fe8019;background-color:#282828}.highlight .x{color:#ebdbb2;background-color:#282828}.highlight .p{color:#ebdbb2;background-color:#282828}.highlight .ch{color:#928374;font-style:italic;background-color:#282828}.highlight .cm{color:#928374;font-style:italic;background-color:#282828}.highlight .cp{color:#8ec07c;background-color:#282828}.highlight .c1{color:#928374;font-style:italic;background-color:#282828}.highlight .cs{color:#928374;font-style:italic;background-color:#282828}.highlight .gd{color:#282828;background-color:#fb4934}.highlight .ge{color:#83a598;text-decoration:underline;background-color:#282828}.highlight .gr{color:#ebdbb2;font-weight:700;background-color:#fb4934}.highlight .gh{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .gi{color:#282828;background-color:#b8bb26}.highlight .go{color:#504945;background-color:#282828}.highlight .gp{color:#ebdbb2;background-color:#282828}.highlight .gs{color:#ebdbb2;background-color:#282828}.highlight .gu{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .gt{color:#ebdbb2;font-weight:700;background-color:#fb4934}.highlight .kc{color:#fe8019;background-color:#282828}.highlight .kd{color:#fe8019;background-color:#282828}.highlight .kn{color:#fe8019;background-color:#282828}.highlight .kp{color:#fe8019;background-color:#282828}.highlight .kr{color:#fe8019;background-color:#282828}.highlight .kt{color:#fabd2f;background-color:#282828}.highlight .ld{color:#ebdbb2;background-color:#282828}.highlight .m{color:#d3869b;background-color:#282828}.highlight .s{color:#b8bb26;background-color:#282828}.highlight .na{color:#b8bb26;font-weight:700;background-color:#282828}.highlight .nb{color:#fabd2f;background-color:#282828}.highlight .nc{color:#ebdbb2;background-color:#282828}.highlight .no{color:#d3869b;background-color:#282828}.highlight .nd{color:#ebdbb2;background-color:#282828}.highlight .ni{color:#fabd2f;background-color:#282828}.highlight .ne{color:#fb4934;background-color:#282828}.highlight .nf{color:#fabd2f;background-color:#282828}.highlight .nl{color:#fb4934;background-color:#282828}.highlight .nn{color:#ebdbb2;background-color:#282828}.highlight .nx{color:#ebdbb2;background-color:#282828}.highlight .py{color:#ebdbb2;background-color:#282828}.highlight .nt{color:#fb4934;background-color:#282828}.highlight .nv{color:#ebdbb2;background-color:#282828}.highlight .ow{color:#fe8019;background-color:#282828}.highlight .w{color:#ebdbb2;background-color:#282828}.highlight .mb{color:#d3869b;background-color:#282828}.highlight .mf{color:#d3869b;background-color:#282828}.highlight .mh{color:#d3869b;background-color:#282828}.highlight .mi{color:#d3869b;background-color:#282828}.highlight .mo{color:#d3869b;background-color:#282828}.highlight .sb{color:#b8bb26;background-color:#282828}.highlight .sc{color:#b8bb26;background-color:#282828}.highlight .sd{color:#b8bb26;background-color:#282828}.highlight .s2{color:#b8bb26;background-color:#282828}.highlight .se{color:#b8bb26;background-color:#282828}.highlight .sh{color:#b8bb26;background-color:#282828}.highlight .si{color:#b8bb26;background-color:#282828}.highlight .sx{color:#b8bb26;background-color:#282828}.highlight .sr{color:#b8bb26;background-color:#282828}.highlight .s1{color:#b8bb26;background-color:#282828}.highlight .ss{color:#83a598;background-color:#282828}.highlight .bp{color:#fabd2f;background-color:#282828}.highlight .vc{color:#ebdbb2;background-color:#282828}.highlight .vg{color:#ebdbb2;background-color:#282828}.highlight .vi{color:#ebdbb2;background-color:#282828}.highlight .il{color:#d3869b;background-color:#282828}
\ No newline at end of file