stagit

static page generator for git
git clone https://github.com/michaelneuper/stagit.git
README | Files | Log | Refs | LICENSE

commit 1f4d720bfa77f81ee9309b1b3276ecff59fe8a33
parent 0decaa4a8d589b1f65d910b3d300ec5b831b6c42
Author: Michael Neuper <michael@michaelneuper.com>
Date:   Sun, 25 Oct 2026 21:20:07 +0200

render the README as the repository's front page

README.md is rendered with md4c (GitHub dialect) into index.html and
preferred over a plain README, which is shown as preformatted text. The
README link moves to the front of the menu.

Diffstat:
M.github/workflows/mirror.yml | 4++--
MMakefile | 3++-
MREADME.md | 9++++++++-
Mstagit.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++----
Mstyle.css | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 144 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Install libgit2 + - name: Install libgit2 and md4c run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends libgit2-dev + sudo apt-get install -y --no-install-recommends libgit2-dev libmd4c-dev libmd4c-html0-dev - name: Build stagit run: make diff --git a/Makefile b/Makefile @@ -11,6 +11,7 @@ DOCPREFIX = ${PREFIX}/share/doc/${NAME} # /opt/homebrew is Homebrew on Apple silicon. LIBGIT_INC = -I/usr/local/include -I/opt/homebrew/include LIBGIT_LIB = -L/usr/local/lib -L/opt/homebrew/lib -lgit2 +MD4C_LIB = -lmd4c-html # use system flags. STAGIT_CFLAGS = ${LIBGIT_INC} ${CFLAGS} @@ -69,7 +70,7 @@ dist: ${OBJ}: ${HDR} stagit: stagit.o ${COMPATOBJ} - ${CC} -o $@ stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS} + ${CC} -o $@ stagit.o ${COMPATOBJ} ${STAGIT_LDFLAGS} ${MD4C_LIB} stagit-index: stagit-index.o ${COMPATOBJ} ${CC} -o $@ stagit-index.o ${COMPATOBJ} ${STAGIT_LDFLAGS} diff --git a/README.md b/README.md @@ -6,6 +6,11 @@ mirror of my public GitHub repos. The upstream README is in `README`. ## Changes from upstream +- Each repo opens on its README (`index.html`), with markdown rendered by + [md4c](https://github.com/mity/md4c). `README.md` is preferred over + `README`, which is shown as plain text. Repos without either open on the + log. +- stagit-index links to each repo's front page instead of its log. - cgit-flavoured `style.css`, and my own logo and favicon. - The Makefile also finds libraries installed by Homebrew. @@ -13,7 +18,7 @@ To see only the code changes: `git diff 1.3 -- '*.c' Makefile`. ## Building the site -Needs libgit2 and the GitHub CLI (`brew install libgit2 gh`). +Needs libgit2, md4c and the GitHub CLI (`brew install libgit2 md4c gh`). ./build.sh python3 -m http.server -d public @@ -78,3 +83,5 @@ Merge rather than rebase, so `main` never needs a force push. Conflicts in existing commit page, so incremental output would go stale after a force push or a description change. Netlify only uploads files whose contents changed, so redeploys stay small. +- Relative image links in READMEs don't work, because stagit only publishes + HTML views of files, not the files themselves. diff --git a/stagit.c b/stagit.c @@ -13,6 +13,7 @@ #include <unistd.h> #include <git2.h> +#include <md4c-html.h> #include "compat.h" @@ -69,7 +70,7 @@ static char cloneurl[1024]; static char *submodules; static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:LICENSE.txt", "HEAD:COPYING" }; static char *license; -static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" }; +static char *readmefiles[] = { "HEAD:README.md", "HEAD:README" }; static char *readme; static long long nlogcommits = -1; /* -1 indicates not used */ @@ -535,15 +536,14 @@ writeheader(FILE *fp, const char *title) fputs("</a></td></tr>", fp); } fputs("<tr><td></td><td>\n", fp); + if (readme) + fprintf(fp, "<a href=\"%sindex.html\">README</a> | ", relpath); 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); @@ -946,6 +946,40 @@ writeatom(FILE *fp, int all) return 0; } +void +writemarkdown(const MD_CHAR *s, MD_SIZE len, void *fp) +{ + fwrite(s, 1, len, fp); +} + +void +writereadme(FILE *fp) +{ + git_object *obj = NULL; + char spec[PATH_MAX]; + const char *s, *ext; + size_t len; + + snprintf(spec, sizeof(spec), "HEAD:%s", readme); + if (git_revparse_single(&obj, repo, spec)) + return; + s = git_blob_rawcontent((git_blob *)obj); + len = git_blob_rawsize((git_blob *)obj); + + fputs("<div id=\"readme\">\n", fp); + if ((ext = strrchr(readme, '.')) && !strcmp(ext, ".md")) { + if (md_html(s, len, writemarkdown, fp, MD_DIALECT_GITHUB, 0)) + errx(1, "md_html: '%s'", readme); + } else { + fputs("<pre>", fp); + xmlencode(fp, s, len); + fputs("</pre>\n", fp); + } + fputs("</div>\n", fp); + + git_object_free(obj); +} + size_t writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize) { @@ -1326,6 +1360,17 @@ main(int argc, char *argv[]) submodules = ".gitmodules"; git_object_free(obj); + /* README for HEAD as the front page */ + if (readme) { + fp = efopen("index.html", "w"); + relpath = ""; + writeheader(fp, ""); + writereadme(fp); + writefooter(fp); + checkfileerror(fp, "index.html", 'w'); + fclose(fp); + } + /* log for HEAD */ fp = efopen("log.html", "w"); relpath = ""; diff --git a/style.css b/style.css @@ -200,3 +200,86 @@ span.d, pre a.d { color: var(--del); } + +/* README front page. */ + +#readme { + max-width: 60em; + line-height: 1.5; +} + +#readme h1, +#readme h2, +#readme h3, +#readme h4 { + margin: 1.2em 0 0.5em; + font-weight: bold; + line-height: 1.25; +} + +#readme h1:first-child, +#readme h2:first-child { + margin-top: 0.5em; +} + +#readme h1, +#readme h2 { + padding-bottom: 0.2em; + border-bottom: 1px solid var(--rule); +} + +#readme h1 { + font-size: 160%; +} + +#readme h2 { + font-size: 130%; +} + +#readme h3 { + font-size: 115%; +} + +#readme h4 { + font-size: 100%; +} + +#readme code { + padding: 0.1em 0.3em; + font: 9pt var(--mono); + background: var(--row); +} + +#readme pre { + padding: 0.6em 0.8em; + overflow-x: auto; + background: var(--row); +} + +#readme pre code { + padding: 0; + font: inherit; + background: none; +} + +#readme img { + max-width: 100%; +} + +#readme blockquote { + margin: 0; + padding: 0 1em; + color: var(--muted); + border-left: 3px solid var(--rule); +} + +#readme th, +#readme td { + padding: 0.2em 0.6em; + white-space: normal; + border: 1px solid var(--rule); +} + +#readme th { + background: var(--row); +}