first commit
This commit is contained in:
commit
6fe2126d61
18 changed files with 3792 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
stagit
|
||||
stagit-index
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT/X Consortium License
|
||||
|
||||
(c) 2015-2021 Hiltjo Posthuma <hiltjo@codemadness.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
109
Makefile
Normal file
109
Makefile
Normal file
|
@ -0,0 +1,109 @@
|
|||
.POSIX:
|
||||
|
||||
NAME = stagit
|
||||
VERSION = 0.9.5
|
||||
|
||||
# paths
|
||||
PREFIX = /usr/local
|
||||
MANPREFIX = ${PREFIX}/man
|
||||
DOCPREFIX = ${PREFIX}/share/doc/${NAME}
|
||||
|
||||
LIBGIT_INC = -I/usr/local/include
|
||||
LIBGIT_LIB = -L/usr/local/lib -lgit2
|
||||
|
||||
# use system flags.
|
||||
STAGIT_CFLAGS = ${LIBGIT_INC} ${CFLAGS}
|
||||
STAGIT_LDFLAGS = ${LIBGIT_LIB} ${LDFLAGS}
|
||||
STAGIT_CPPFLAGS = -D_XOPEN_SOURCE=700 -D_DEFAULT_SOURCE -D_BSD_SOURCE
|
||||
|
||||
SRC = \
|
||||
stagit.c\
|
||||
stagit-index.c
|
||||
COMPATSRC = \
|
||||
reallocarray.c\
|
||||
strlcat.c\
|
||||
strlcpy.c
|
||||
BIN = \
|
||||
stagit\
|
||||
stagit-index
|
||||
MAN1 = \
|
||||
stagit.1\
|
||||
stagit-index.1
|
||||
DOC = \
|
||||
LICENSE\
|
||||
README
|
||||
HDR = compat.h
|
||||
|
||||
COMPATOBJ = \
|
||||
reallocarray.o\
|
||||
strlcat.o\
|
||||
strlcpy.o
|
||||
|
||||
OBJ = ${SRC:.c=.o} ${COMPATOBJ}
|
||||
|
||||
all: ${BIN}
|
||||
|
||||
.o:
|
||||
${CC} -o $@ ${LDFLAGS}
|
||||
|
||||
.c.o:
|
||||
${CC} -o $@ -c $< ${STAGIT_CFLAGS} ${STAGIT_CPPFLAGS}
|
||||
|
||||
dist:
|
||||
rm -rf ${NAME}-${VERSION}
|
||||
mkdir -p ${NAME}-${VERSION}
|
||||
cp -f ${MAN1} ${HDR} ${SRC} ${COMPATSRC} ${DOC} \
|
||||
Makefile favicon.png logo.png style.css \
|
||||
example_create.sh example_post-receive.sh \
|
||||
${NAME}-${VERSION}
|
||||
# make tarball
|
||||
tar -cf - ${NAME}-${VERSION} | \
|
||||
gzip -c > ${NAME}-${VERSION}.tar.gz
|
||||
rm -rf ${NAME}-${VERSION}
|
||||
|
||||
${OBJ}: ${HDR}
|
||||
|
||||
stagit: stagit.o ${COMPATOBJ}
|
||||
${CC} -o $@ stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
|
||||
|
||||
stagit-index: stagit-index.o ${COMPATOBJ}
|
||||
${CC} -o $@ stagit-index.o ${COMPATOBJ} ${STAGIT_LDFLAGS}
|
||||
|
||||
clean:
|
||||
rm -f ${BIN} ${OBJ} ${NAME}-${VERSION}.tar.gz
|
||||
|
||||
install: all
|
||||
# installing executable files.
|
||||
mkdir -p ${DESTDIR}${PREFIX}/bin
|
||||
cp -f ${BIN} ${DESTDIR}${PREFIX}/bin
|
||||
for f in ${BIN}; do chmod 755 ${DESTDIR}${PREFIX}/bin/$$f; done
|
||||
# installing example files.
|
||||
mkdir -p ${DESTDIR}${DOCPREFIX}
|
||||
cp -f style.css\
|
||||
favicon.png\
|
||||
logo.png\
|
||||
example_create.sh\
|
||||
example_post-receive.sh\
|
||||
README\
|
||||
${DESTDIR}${DOCPREFIX}
|
||||
# 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
|
||||
|
||||
uninstall:
|
||||
# removing executable files.
|
||||
for f in ${BIN}; do rm -f ${DESTDIR}${PREFIX}/bin/$$f; done
|
||||
# removing example files.
|
||||
rm -f \
|
||||
${DESTDIR}${DOCPREFIX}/style.css\
|
||||
${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}
|
||||
# removing manual pages.
|
||||
for m in ${MAN1}; do rm -f ${DESTDIR}${MANPREFIX}/man1/$$m; done
|
||||
|
||||
.PHONY: all clean dist install uninstall
|
181
README
Normal file
181
README
Normal file
|
@ -0,0 +1,181 @@
|
|||
stagit
|
||||
------
|
||||
|
||||
static git page generator.
|
||||
|
||||
It generates static HTML pages for a git repository.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Make files per repository:
|
||||
|
||||
$ mkdir -p htmldir && cd htmldir
|
||||
$ stagit path-to-repo
|
||||
|
||||
Make index file for repositories:
|
||||
|
||||
$ stagit-index repodir1 repodir2 repodir3 > index.html
|
||||
|
||||
|
||||
Build and install
|
||||
-----------------
|
||||
|
||||
$ make
|
||||
# make install
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
- C compiler (C99).
|
||||
- libc (tested with OpenBSD, FreeBSD, NetBSD, Linux: glibc and musl).
|
||||
- libgit2 (v0.22+).
|
||||
- POSIX make (optional).
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
See man pages: stagit(1) and stagit-index(1).
|
||||
|
||||
|
||||
Building a static binary
|
||||
------------------------
|
||||
|
||||
It may be useful to build static binaries, for example to run in a chroot.
|
||||
|
||||
It can be done like this at the time of writing (v0.24):
|
||||
|
||||
cd libgit2-src
|
||||
|
||||
# change the options in the CMake file: CMakeLists.txt
|
||||
BUILD_SHARED_LIBS to OFF (static)
|
||||
CURL to OFF (not needed)
|
||||
USE_SSH OFF (not needed)
|
||||
THREADSAFE OFF (not needed)
|
||||
USE_OPENSSL OFF (not needed, use builtin)
|
||||
|
||||
mkdir -p build && cd build
|
||||
cmake ../
|
||||
make
|
||||
make install
|
||||
|
||||
|
||||
Extract owner field from git config
|
||||
-----------------------------------
|
||||
|
||||
A way to extract the gitweb owner for example in the format:
|
||||
|
||||
[gitweb]
|
||||
owner = Name here
|
||||
|
||||
Script:
|
||||
|
||||
#!/bin/sh
|
||||
awk '/^[ ]*owner[ ]=/ {
|
||||
sub(/^[^=]*=[ ]*/, "");
|
||||
print $0;
|
||||
}'
|
||||
|
||||
|
||||
Set clone url for a directory of repos
|
||||
--------------------------------------
|
||||
#!/bin/sh
|
||||
cd "$dir"
|
||||
for i in *; do
|
||||
test -d "$i" && echo "git://git.codemadness.org/$i" > "$i/url"
|
||||
done
|
||||
|
||||
|
||||
Update files on git push
|
||||
------------------------
|
||||
|
||||
Using a post-receive hook the static files can be automatically updated.
|
||||
Keep in mind git push -f can change the history and the commits may need
|
||||
to be recreated. This is because stagit checks if a commit file already
|
||||
exists. It also has a cache (-c) option which can conflict with the new
|
||||
history. See stagit(1).
|
||||
|
||||
git post-receive hook (repo/.git/hooks/post-receive):
|
||||
|
||||
#!/bin/sh
|
||||
# detect git push -f
|
||||
force=0
|
||||
while read -r old new ref; do
|
||||
hasrevs=$(git rev-list "$old" "^$new" | sed 1q)
|
||||
if test -n "$hasrevs"; then
|
||||
force=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# remove commits and .cache on git push -f
|
||||
#if test "$force" = "1"; then
|
||||
# ...
|
||||
#fi
|
||||
|
||||
# see example_create.sh for normal creation of the files.
|
||||
|
||||
|
||||
Create .tar.gz archives by tag
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
name="stagit"
|
||||
mkdir -p archives
|
||||
git tag -l | while read -r t; do
|
||||
f="archives/${name}-$(echo "${t}" | tr '/' '_').tar.gz"
|
||||
test -f "${f}" && continue
|
||||
git archive \
|
||||
--format tar.gz \
|
||||
--prefix "${t}/" \
|
||||
-o "${f}" \
|
||||
-- \
|
||||
"${t}"
|
||||
done
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Log of all commits from HEAD.
|
||||
- Log and diffstat per commit.
|
||||
- Show file tree with linkable line numbers.
|
||||
- Show references: local branches and tags.
|
||||
- Detect README and LICENSE file from HEAD and link it as a webpage.
|
||||
- Detect submodules (.gitmodules file) from HEAD and link it as a webpage.
|
||||
- Atom feed of the commit log (atom.xml).
|
||||
- Atom feed of the tags/refs (tags.xml).
|
||||
- Make index page for multiple repositories with stagit-index.
|
||||
- After generating the pages (relatively slow) serving the files is very fast,
|
||||
simple and requires little resources (because the content is static), only
|
||||
a HTTP file server is required.
|
||||
- Usable with text-browsers such as dillo, links, lynx and w3m.
|
||||
|
||||
|
||||
Cons
|
||||
----
|
||||
|
||||
- Not suitable for large repositories (2000+ commits), because diffstats are
|
||||
an expensive operation, the cache (-c flag) is a workaround for this in
|
||||
some cases.
|
||||
- Not suitable for large repositories with many files, because all files are
|
||||
written for each execution of stagit. This is because stagit shows the lines
|
||||
of textfiles and there is no "cache" for file metadata (this would add more
|
||||
complexity to the code).
|
||||
- Not suitable for repositories with many branches, a quite linear history is
|
||||
assumed (from HEAD).
|
||||
|
||||
In these cases it is better to just use cgit or possibly change stagit to
|
||||
run as a CGI program.
|
||||
|
||||
- Relatively slow to run the first time (about 3 seconds for sbase,
|
||||
1500+ commits), incremental updates are faster.
|
||||
- Does not support some of the dynamic features cgit has, like:
|
||||
- Snapshot tarballs per commit.
|
||||
- File tree per commit.
|
||||
- History log of branches diverged from HEAD.
|
||||
- Stats (git shortlog -s).
|
||||
|
||||
This is by design, just use git locally.
|
6
compat.h
Normal file
6
compat.h
Normal file
|
@ -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);
|
43
example_create.sh
Executable file
43
example_create.sh
Executable file
|
@ -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" -u "https://git.codemadness.nl/$d/" "${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
|
73
example_post-receive.sh
Executable file
73
example_post-receive.sh
Executable file
|
@ -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}" -u "https://git.codemadness.nl/$d/" "${reposdir}/${r}"
|
||||
|
||||
ln -sf log.html index.html
|
||||
ln -sf ../style.css style.css
|
||||
ln -sf ../logo.png logo.png
|
||||
|
||||
echo "done"
|
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 505 B |
BIN
logo.png
Normal file
BIN
logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 505 B |
39
reallocarray.c
Normal file
39
reallocarray.c
Normal file
|
@ -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);
|
||||
}
|
42
stagit-index.1
Normal file
42
stagit-index.1
Normal file
|
@ -0,0 +1,42 @@
|
|||
.Dd December 26, 2015
|
||||
.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
|
216
stagit-index.c
Normal file
216
stagit-index.c
Normal file
|
@ -0,0 +1,216 @@
|
|||
#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("<", fp); break;
|
||||
case '>': fputs(">", fp); break;
|
||||
case '\'': fputs("'" , fp); break;
|
||||
case '&': fputs("&", fp); break;
|
||||
case '"': fputs(""", fp); break;
|
||||
default: putc(*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__
|
||||
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;
|
||||
}
|
115
stagit.1
Normal file
115
stagit.1
Normal file
|
@ -0,0 +1,115 @@
|
|||
.Dd March 5, 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
|
||||
.Op Fl u Ar baseurl
|
||||
.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.
|
||||
.It Fl u Ar baseurl
|
||||
Base URL to make links in the Atom feeds absolute.
|
||||
For example: "https://git.codemadness.org/stagit/".
|
||||
.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 of the last 100 commits.
|
||||
.It tags.xml
|
||||
Atom XML feed of the tags.
|
||||
.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 EXIT STATUS
|
||||
.Ex -std
|
||||
.Sh SEE ALSO
|
||||
.Xr stagit-index 1
|
||||
.Sh AUTHORS
|
||||
.An Hiltjo Posthuma Aq Mt hiltjo@codemadness.org
|
1360
stagit.c.bak
Normal file
1360
stagit.c.bak
Normal file
File diff suppressed because it is too large
Load diff
57
strlcat.c
Normal file
57
strlcat.c
Normal file
|
@ -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 */
|
||||
}
|
52
strlcpy.c
Normal file
52
strlcpy.c
Normal file
|
@ -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 */
|
||||
}
|
106
style.css
Normal file
106
style.css
Normal file
|
@ -0,0 +1,106 @@
|
|||
body {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img, h1, h2 {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a:target {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
a.d,
|
||||
a.h,
|
||||
a.i,
|
||||
a.line {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#blob a {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
#blob a:hover {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
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: #eee;
|
||||
}
|
||||
|
||||
#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: #555;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-top: 1px solid #555;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
pre a.h {
|
||||
color: #00a;
|
||||
}
|
||||
|
||||
.A,
|
||||
span.i,
|
||||
pre a.i {
|
||||
color: #070;
|
||||
}
|
||||
|
||||
.D,
|
||||
span.d,
|
||||
pre a.d {
|
||||
color: #e00;
|
||||
}
|
||||
|
||||
pre a.h:hover,
|
||||
pre a.i:hover,
|
||||
pre a.d:hover {
|
||||
text-decoration: none;
|
||||
}
|
Loading…
Reference in a new issue