Personal build of stagit static git page generator.
log | files | refs | readme | license

commit 67dc0e3b64a5ba41b921e64d6350d377f7a3da0b
parent 6b5445a8fe009ef9597311dca90f2add4185e8fa
Author: Michael Skec
Date:   Fri, 10 Nov 2023 16:38:39 +1100

Fixes and improvements

E-mail is now redacted from commits and Atom feeds.  Some stylistic
changes, and bug fixes.

Diffstat:
MREADME | 188+++----------------------------------------------------------------------------
Mstagit-create.sh | 2+-
Mstagit-index.c | 14++++++++++----
Mstagit.c | 60+++++++++++++++++++++++++++++++++++++++++++++---------------
Mstyle.css | 58+++++++---------------------------------------------------
5 files changed, 70 insertions(+), 252 deletions(-)

diff --git a/README b/README @@ -1,186 +1,12 @@ -stagit ------- - -static git page generator. - -It generates static HTML pages for a git repository. - - -Usage ------ - -Make files per repository: - - $ mkdir -p htmlroot/htmlrepo1 && cd htmlroot/htmlrepo1 - $ stagit path/to/gitrepo1 - repeat for other repositories - $ ... - -Make index file for repositories: - - $ cd htmlroot - $ stagit-index path/to/gitrepo1 \ - path/to/gitrepo2 \ - path/to/gitrepo3 > 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 +stagit-custom ------------- -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). +My personal build of stagit static git page generator. The original repository +for stagit is located at: - In these cases it is better to just use cgit or possibly change stagit to - run as a CGI program. + https://git.codemadness.org/stagit -- 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). +My build is literally just a bunch of hacks on top of the existing codebase to +make it generate pages how I want them, with no concern whatsoever for keeping +the code clean. - This is by design, just use git locally. diff --git a/stagit-create.sh b/stagit-create.sh @@ -29,7 +29,7 @@ for dir in "${reposdir}/pub/"*/; do mkdir -p "${curdir}/pub/${d}" cd "${curdir}/pub/${d}" || continue - stagit -c ".cache" -u "https://git.skec.site/$d/" "${reposdir}/pub/${d}" + stagit -c ".cache" -u "https://git.skec.site/pub/${d}/" "${reposdir}/pub/${d}" # symlinks ln -sf log.html index.html diff --git a/stagit-index.c b/stagit-index.c @@ -12,7 +12,8 @@ static git_repository *repo; static const char *relpath = ""; -static char description[255] = "skec.site git repositories"; +static char title[255] = "skec.site git repositories"; +static char description[255] = "<a href=\"https://skec.site/\">skec.site</a> git repositories</a>"; static char *name = ""; static char owner[255]; @@ -100,18 +101,23 @@ writeheader(FILE *fp) "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" "<title>", fp); - xmlencode(fp, description, strlen(description)); + xmlencode(fp, title, strlen(title)); 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); +#if LOGO 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)); +#else + fputs("<table>\n<tr>\n<td><span class=\"desc\">", fp); +#endif + //xmlencode(fp, description, strlen(description)); + fputs(description, fp); 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>" + "<td><b>Latest commit</b></td></tr>" "</thead><tbody>\n", fp); } diff --git a/stagit.c b/stagit.c @@ -18,6 +18,9 @@ #define LEN(s) (sizeof(s)/sizeof(*s)) +#define LOGO 0 +#define REDACT_EMAIL 1 + struct deltainfo { git_patch *patch; @@ -521,41 +524,55 @@ writeheader(FILE *fp, const char *title) xmlencode(fp, name, strlen(name)); fprintf(fp, " Atom Feed (tags)\" href=\"%stags.xml\" />\n", relpath); fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath); - fputs("</head>\n<body>\n<table><tr><td>", fp); + fputs("</head>\n<body>\n<table id=\"headertable\"><tr><td class=\"headerlink\">", fp); +#if LOGO fprintf(fp, "<a href=\"/\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>", relpath); + fputs("<td></td>", fp); +#else + fputs("<a href=\"/\">index</a>", fp); + fputs("<td class=\"headerlink\">:</td>", fp); +#endif + #if 0 fputs("</td><td><h1>", fp); xmlencode(fp, name, strlen(name)); - fputs("</h1><span class=\"desc\">", fp); + fputs("</h1>", fp); #else - fputs("</td><td><a id=\"title\" title=\"", fp); + fputs("</td><td class=\"headerlink\"><a title=\"", fp); xmlencode(fp, name, strlen(name)); fprintf(fp, "\" href=\"/pub/%s\">", name); xmlencode(fp, name, strlen(name)); - fputs("</a><span class=\"desc\">", fp); + fputs("</a>", fp); #endif - xmlencode(fp, description, strlen(description)); - fputs("</span></td></tr>", fp); + fputs("</td></tr>", fp); + + if (description[0]) + { + fputs("<tr class=\"desc\"><td></td><td></td><td>", fp); + xmlencode(fp, description, strlen(description)); + fputs("</td></tr>", fp); + } + if (cloneurl[0]) { - fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp); + fputs("<tr class=\"url\"><td></td><td></td><td>git clone <a href=\"", fp); xmlencode(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */ fputs("\">", fp); xmlencode(fp, cloneurl, strlen(cloneurl)); fputs("</a></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); + fputs("<tr><td></td><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>", + fprintf(fp, " | <a href=\"%sfile/%s.html\">submodules</a>", relpath, submodules); if (readme) - fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>", + fprintf(fp, " | <a href=\"%sfile/%s.html\">readme</a>", relpath, readme); if (license) - fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>", + fprintf(fp, " | <a href=\"%sfile/%s.html\">license</a>", relpath, license); fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp); } @@ -665,11 +682,16 @@ printcommit(FILE *fp, struct commitinfo *ci) if (ci->author) { fputs("<b>Author:</b> ", fp); xmlencode(fp, ci->author->name, strlen(ci->author->name)); + #if REDACT_EMAIL + //fputs(" &lt;REDACTED&gt;\n<b>Date:</b> ", fp); + fputs("\n<b>Date:</b> ", fp); + #else fputs(" &lt;<a href=\"mailto:", fp); xmlencode(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */ fputs("\">", fp); xmlencode(fp, ci->author->email, strlen(ci->author->email)); fputs("</a>&gt;\n<b>Date:</b> ", fp); + #endif printtime(fp, &(ci->author->when)); putc('\n', fp); } @@ -938,8 +960,12 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) if (ci->author) { fputs("<author>\n<name>", fp); xmlencode(fp, ci->author->name, strlen(ci->author->name)); + #if REDACT_EMAIL + fputs("</name>\n<email>REDACTED", fp); + #else fputs("</name>\n<email>", fp); xmlencode(fp, ci->author->email, strlen(ci->author->email)); + #endif fputs("</email>\n</author>\n", fp); } @@ -950,9 +976,13 @@ printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) if (ci->author) { fputs("Author: ", fp); xmlencode(fp, ci->author->name, strlen(ci->author->name)); + #if REDACT_EMAIL + fputs("\nDate: ", fp); + #else fputs(" &lt;", fp); xmlencode(fp, ci->author->email, strlen(ci->author->email)); fputs("&gt;\nDate: ", fp); + #endif printtime(fp, &(ci->author->when)); putc('\n', fp); } @@ -1212,7 +1242,7 @@ writerefs(FILE *fp) 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>Latest commit</b></td>" "<td><b>Author</b></td>\n</tr>\n" "</thead><tbody>\n", titles[j], ids[j]); diff --git a/style.css b/style.css @@ -37,12 +37,16 @@ a.line { text-decoration: none; } -#title { +table#headertable td { + padding: 0 !important; +} + +td.headerlink, td.headerlink a { color: #000; text-decoration: none; - font-size: 150%; + font-weight: bold; } -#title:hover { +td.headerlink a:hover { text-decoration: underline; } @@ -134,51 +138,3 @@ pre a.d:hover { padding: 0 0 0 0.5em; vertical-align: top; } - -@media (prefers-color-scheme: dark) { - body { - background-color: #000; - color: #bdbdbd; - } - hr { - border-color: #222; - } - a { - color: #56c8ff; - } - a:target { - background-color: #222; - } - .desc { - color: #aaa; - } - #blob a { - color: #555; - } - #blob a:target { - color: #eee; - } - #blob a:hover { - color: #56c8ff; - } - pre a.h { - color: #00cdcd; - } - .A, - span.i, - pre a.i { - color: #00cd00; - } - .D, - span.d, - pre a.d { - color: #cd0000; - } - #branches tr:hover td, - #tags tr:hover td, - #index tr:hover td, - #log tr:hover td, - #files tr:hover td { - background-color: #111; - } -}